diff --git a/.mono/registry/CurrentUser/software/microsoft/csdevkit/v1/pids/973_133561931041817360/values.xml b/.mono/registry/CurrentUser/software/microsoft/csdevkit/v1/pids/973_133561931041817360/values.xml new file mode 100644 index 0000000..34618ea --- /dev/null +++ b/.mono/registry/CurrentUser/software/microsoft/csdevkit/v1/pids/973_133561931041817360/values.xml @@ -0,0 +1,3 @@ + + 133562849028924050 + \ No newline at end of file diff --git a/CaPPMS/.DS_Store b/CaPPMS/.DS_Store new file mode 100644 index 0000000..5916bec Binary files /dev/null and b/CaPPMS/.DS_Store differ diff --git a/CaPPMS/CaPPMS.csproj b/CaPPMS/CaPPMS.csproj index 50ee775..2e6db67 100644 --- a/CaPPMS/CaPPMS.csproj +++ b/CaPPMS/CaPPMS.csproj @@ -29,22 +29,24 @@ portable - TRACE;RELEASE;NET;NET8_0;NETCOREAPP + TRACE;RELEASE;NET;net8.0 1701;1702 - + - - + + - - - - + + + + \ No newline at end of file diff --git a/CaPPMS/Data/GitHubService.cs b/CaPPMS/Data/GitHubService.cs index f8cf490..9ac753a 100644 --- a/CaPPMS/Data/GitHubService.cs +++ b/CaPPMS/Data/GitHubService.cs @@ -61,7 +61,11 @@ await Task.Run(async () => { try { - var repoUpdateVar = new RepositoryUpdate(RepoName) { DefaultBranch = developmentBranch }; + var repoUpdateVar = new RepositoryUpdate() + { + DefaultBranch = developmentBranch, + Name = RepoName + }; await gitHubClient.Repository.Edit(OrganizationName, RepoName, repoUpdateVar); } catch (AggregateException e) @@ -90,38 +94,42 @@ await Task.Run(async () => }).ConfigureAwait(false); } - public async Task DoAllTasks(string OrganizationName, string RepoName, string Description) + public async Task DoAllTasks(string organizationName, string repoName, string description) { string error = string.Empty; try { - var repository = new NewRepository(RepoName) + var repository = new NewRepository(repoName) { AutoInit = true, - Description = Description, + Description = description, Private = false }; - var newRepository = await gitHubClient.Repository.Create(OrganizationName, repository); + var newRepository = await gitHubClient.Repository.Create(organizationName, repository); - var masterReference = await gitHubClient.Git.Reference.Get(OrganizationName, RepoName, heads + mainBranch); + var masterReference = await gitHubClient.Git.Reference.Get(organizationName, repoName, heads + mainBranch); var branchReference = new NewReference(heads + developmentBranch, masterReference.Object.Sha); - _ = gitHubClient.Git.Reference.Create(OrganizationName, RepoName, branchReference); + _ = gitHubClient.Git.Reference.Create(organizationName, repoName, branchReference); - var repoUpdateVar = new RepositoryUpdate(RepoName) { DefaultBranch = developmentBranch }; - _ = gitHubClient.Repository.Edit(OrganizationName, RepoName, repoUpdateVar); + var repoUpdateVar = new RepositoryUpdate() + { + DefaultBranch = developmentBranch, + Name = repoName, + }; + _ = gitHubClient.Repository.Edit(organizationName, repoName, repoUpdateVar); var protection = new BranchProtectionSettingsUpdate( new BranchProtectionRequiredReviewsUpdate(false, true, 1)); - _ = gitHubClient.Repository.Branch.UpdateBranchProtection(OrganizationName, RepoName, mainBranch, protection); - _ = gitHubClient.Repository.Branch.UpdateBranchProtection(OrganizationName, RepoName, developmentBranch, protection); + _ = gitHubClient.Repository.Branch.UpdateBranchProtection(organizationName, repoName, mainBranch, protection); + _ = gitHubClient.Repository.Branch.UpdateBranchProtection(organizationName, repoName, developmentBranch, protection); } catch (Exception e) { - error = $"Repository:{RepoName} cannot be created. Error: {e.Message}"; + error = $"Repository:{repoName} cannot be created. Error: {e.Message}"; } return error; diff --git a/CaPPMS/Data/IMailService.cs b/CaPPMS/Data/IMailService.cs new file mode 100644 index 0000000..3e5e44f --- /dev/null +++ b/CaPPMS/Data/IMailService.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +namespace CaPPMS.Data +{ + public interface IMailService + { + Task SendEmailAsync(string ToEmail, string Subject, string HTMLBody); + } +} diff --git a/CaPPMS/Data/MailService.cs b/CaPPMS/Data/MailService.cs new file mode 100644 index 0000000..426ff0a --- /dev/null +++ b/CaPPMS/Data/MailService.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Net; +using System.Net.Mail; +namespace CaPPMS.Data +{ + public class MailService : IMailService + { + private readonly MailSettings _mailConfig; + public MailService(MailSettings mailConfig) + { + _mailConfig = mailConfig; + } + + public async Task SendEmailAsync(string ToEmail, string Subject, string HTMLBody) + { + MailMessage message = new MailMessage(); + SmtpClient smtp = new SmtpClient(); + message.From = new MailAddress(_mailConfig.FromEmail); + message.To.Add(new MailAddress(ToEmail)); + message.Subject = Subject; + message.IsBodyHtml = true; + message.Body = HTMLBody; + smtp.Port = _mailConfig.Port; + smtp.Host = _mailConfig.Host; + smtp.EnableSsl = true; + smtp.UseDefaultCredentials = false; + smtp.Credentials = new NetworkCredential(_mailConfig.Username, _mailConfig.Password); + smtp.DeliveryMethod = SmtpDeliveryMethod.Network; + await smtp.SendMailAsync(message); + } + } +} diff --git a/CaPPMS/Data/MailSettings.cs b/CaPPMS/Data/MailSettings.cs new file mode 100644 index 0000000..8caff2e --- /dev/null +++ b/CaPPMS/Data/MailSettings.cs @@ -0,0 +1,11 @@ +namespace CaPPMS.Data +{ + public class MailSettings + { + public string Username { get; set; } + public string Password { get; set; } + public int Port { get; set; } + public string FromEmail { get; set; } + public string Host { get; set; } + } +} diff --git a/CaPPMS/Data/ProjectManagerService.cs b/CaPPMS/Data/ProjectManagerService.cs index 05f0509..ce4740c 100644 --- a/CaPPMS/Data/ProjectManagerService.cs +++ b/CaPPMS/Data/ProjectManagerService.cs @@ -80,6 +80,12 @@ public async Task AddAsync(ProjectInformation idea) ProjectIdeasChanged?.Invoke(ProjectIdeas.Values, EventArgs.Empty); return true; } + + public void NotifyProjectIdeasChanged() + { + ProjectIdeasChanged?.Invoke(this, EventArgs.Empty); + } + public async Task RemoveAsync(ProjectInformation idea, IPrincipal user) { @@ -213,7 +219,6 @@ public async Task ExportAsync(ProjectInformation idea) propertyTitleChunk.SetUnderline(2f, -2f); var itemTitle = new Paragraph(propertyTitleChunk); itemTitle.SpacingAfter = 1f; - itemTitle.Add(Environment.NewLine); var item = new Phrase($"\t\t{exportedField.Item2}", bodyFont); itemTitle.Add(item); diff --git a/CaPPMS/Model/ManageProjectInformation.cs b/CaPPMS/Model/ManageProjectInformation.cs new file mode 100644 index 0000000..a613ccc --- /dev/null +++ b/CaPPMS/Model/ManageProjectInformation.cs @@ -0,0 +1,385 @@ +using Newtonsoft.Json; +using System; +using System.ComponentModel.DataAnnotations; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using CaPPMS.Attributes; + +namespace CaPPMS.Model +{ + public class ManageProjectInformation + { + [Export] + [Browsable(false)] + public Guid ProjectID { get; set; } = Guid.NewGuid(); + + private string projectTitle = string.Empty; + + private string videoLink = string.Empty; + + [Export(true)] + [DisplayName("Video Link")] + [Browsable(true)] + public string VideoLink + { + get + { + return this.videoLink; + } + set + { + this.videoLink = value; + this.IsDirty = true; + } + } + + [Export(true)] + [Required] + [StringLength(256, ErrorMessage = "Title is either too short or too long. We have confidence you can figure out which.", MinimumLength = 5)] + [DisplayName("Project Title")] + [Browsable(true)] + [ColumnHeader] + public string ProjectTitle + { + get + { + return this.projectTitle; + } + set + { + this.projectTitle = value; + this.IsDirty = true; + } + } + + private string projectDescription = string.Empty; + + [Export(true)] + [Required] + [DisplayName("Project Description")] + [Browsable(true)] + [ColumnHeader] + public string ProjectDescription + { + get + { + return this.projectDescription; + } + set + { + this.projectDescription = value; + this.IsDirty = true; + } + } + + private string semesterTerm = string.Empty; + + [Export(true)] + [DisplayName("Semester Term")] + [Browsable(true)] + public string SemesterTerm + { + get + { + return this.semesterTerm; + } + set + { + this.semesterTerm = value; + this.IsDirty = true; + } + } + + private string semesterYear = string.Empty; + + [Export(true)] + [DisplayName("Semester Year")] + [Browsable(true)] + public string SemesterYear + { + get + { + return this.semesterYear; + } + set + { + this.semesterYear = value; + this.IsDirty = true; + } + } + + private string url = string.Empty; + + [Export(true)] + [DisplayName("Project Website")] + [Browsable(true)] + public string Url + { + get + { + return this.url; + } + set + { + this.url = value; + this.IsDirty = true; + } + } + + private string gitUrl = string.Empty; + + [Export(true)] + [DisplayName("GitHub")] + [Browsable(true)] + [SpanIcon("fab fa-github", true)] + public string Github + { + get + { + return this.gitUrl; + } + set + { + this.gitUrl = value; + this.IsDirty = true; + } + } + + [Export] + [Browsable(false)] + private Contact Sponsor { get; set; } = new(); + + [Export] + [Browsable(false)] + private Contact Submitter { get; set; } = new(); + + [Export(true)] + [Required] + [StringLength(50, ErrorMessage = "First name is too long.")] + [DisplayName("First Name")] + [Browsable(true)] + public string FirstName + { + get + { + return this.Submitter.FirstName; + } + set + { + this.Submitter.FirstName = value; + this.IsDirty = true; + } + } + + [Export(true)] + [Required] + [StringLength(50, ErrorMessage = "Last name is too long.")] + [DisplayName("Last Name")] + [Browsable(true)] + public string LastName + { + get + { + return this.Submitter.LastName; + } + set + { + this.Submitter.LastName = value; + this.IsDirty = true; + } + } + + [Export(true)] + [EmailAddress] + [Browsable(true)] + public string Email + { + get + { + return this.Submitter.Email; + } + set + { + this.Submitter.Email = value; + this.IsDirty = true; + } + } + + [Export(true)] + [Phone] + [Browsable(true)] + public string Phone + { + get + { + return this.Submitter.Phone; + } + set + { + this.Submitter.Phone = value; + this.IsDirty = true; + } + } + + // We will export attachments manually. + [Browsable(true)] + // Matches the config. + [AttachmentsNumFilesValidator(50)] + // Matches the config value. Value is in Mb + [AttachmentsFileSizeValidator(10)] + public IList Attachments { get; private set; } = new List(); + + public List CompletedDocuments { get; set; } = new List(); + + + [DisplayName("Are you the sponsor")] + [Browsable(true)] + public bool IsSponsor { get; set; } = true; + + [Export(true)] + [Required] + [StringLength(255, ErrorMessage = "Sponsor first name is too long.")] + [DisplayName("Sponsor First Name")] + [Browsable(true)] + public string SponsorFirstName + { + get + { + return this.Sponsor.FirstName; + } + set + { + this.Sponsor.FirstName = value; + this.IsDirty = true; + } + } + + [Export(true)] + [Required] + [StringLength(255, ErrorMessage = "Sponsor last name is too long.")] + [DisplayName("Sponsor Last Name")] + [Browsable(true)] + public string SponsorLastName + { + get + { + return this.Sponsor.LastName; + } + set + { + this.Sponsor.LastName = value; + this.IsDirty = true; + } + } + + [Export(true)] + [EmailAddress] + [DisplayName("Sponsor Email")] + [Browsable(true)] + public string SponsorEmail + { + get + { + return this.Sponsor.Email; + } + set + { + this.Sponsor.Email = value; + this.IsDirty = true; + } + } + + [Export(true)] + [Phone] + [DisplayName("Sponsor Phone")] + [Browsable(true)] + public string SponsorPhone + { + get + { + return this.Sponsor.Phone; + } + set + { + this.Sponsor.Phone = value; + this.IsDirty = true; + } + } + + private string status = "New"; + + [ColumnHeader] + [Browsable(true)] + public string Status + { + get + { + return this.status; + } + set + { + this.status = value; + this.IsDirty = true; + } + } + + [Export(false)] + [DisplayName("Comments:")] + public Comments Comments { get; } = new Comments(); + + [Browsable(false)] + [JsonIgnore] + public bool IsDirty { get; set; } = false; + + public void SetAttachments(IList files) + { + if (files.Count == 0) + { + this.IsDirty = false; + return; + } + + this.Attachments.Clear(); + + foreach(var file in files) + { + this.Attachments.Add(file as ProjectFile); + } + + this.IsDirty = true; + } + + public void AddAttachments(IList files) + { + if (files.Count == 0) + { + this.IsDirty = false; + + return; + } + + foreach (var file in files) + { + this.Attachments.Add(file as ProjectFile); + } + + this.IsDirty = true; + } + + public IEnumerable> GetExportableInformation() + { + foreach(var prop in this.GetType().GetProperties()) + { + var canExport = prop.GetCustomAttribute()?.CanExport ?? false; + + if (canExport) + { + string name = prop.GetCustomAttribute()?.DisplayName ?? prop.Name; + + yield return Tuple.Create(name, prop.GetValue(this)); + } + } + } + } +} diff --git a/CaPPMS/Model/ProjectInformation.cs b/CaPPMS/Model/ProjectInformation.cs index 91937d9..91b8c91 100644 --- a/CaPPMS/Model/ProjectInformation.cs +++ b/CaPPMS/Model/ProjectInformation.cs @@ -16,6 +16,41 @@ public class ProjectInformation private string projectTitle = string.Empty; + // private string teamMembers = string.Empty; + + // [Export(true)] + // [DisplayName("Team Members")] + // [Browsable(true)] + // public string TeamMember + // { + // get + // { + // return this.teamMembers; + // } + // set + // { + // this.teamMembers = value; + // this.IsDirty = true; + // } + // } + + + // // [Export(true)] + // // [DisplayName("Team Members")] + // // [Browsable(true)] + private string teamMember = string.Empty; + + public string TeamMember { + get + { + return this.teamMember; + } + set{ + this.teamMember = value; + this.IsDirty = true; + } + } + private string videoLink = string.Empty; [Export(true)] @@ -53,6 +88,7 @@ public string ProjectTitle } } + private string projectDescription = string.Empty; [Export(true)] @@ -73,6 +109,9 @@ public string ProjectDescription } } + + + private string semesterTerm = string.Empty; [Export(true)] @@ -109,6 +148,43 @@ public string SemesterYear } } + + private string linkName = string.Empty; + + [Export(true)] + [DisplayName("Link Name")] + [Browsable(true)] + public string LinkName + { + get + { + return this.linkName; + } + set + { + this.linkName = value; + this.IsDirty = true; + } + } + + // private string linkNames = string.Empty; + + // [Export(true)] + // [DisplayName("Link Name")] + // [Browsable(true)] + // public string LinkNames + // { + // get + // { + // return this.linkNames; + // } + // set + // { + // this.linkNames = value; + // this.IsDirty = true; + // } + // } + private string url = string.Empty; [Export(true)] @@ -127,6 +203,25 @@ public string Url } } + // private string urls = string.Empty; + + // [Export(true)] + // [DisplayName("Project Website")] + // [Browsable(true)] + // public string Urls + // { + // get + // { + // return this.urls; + // } + // set + // { + // this.urls = value; + // this.IsDirty = true; + // } + // } + + private string gitUrl = string.Empty; [Export(true)] @@ -191,6 +286,7 @@ public string LastName } [Export(true)] + [Required] [EmailAddress] [Browsable(true)] public string Email @@ -207,7 +303,6 @@ public string Email } [Export(true)] - [Phone] [Browsable(true)] public string Phone { @@ -231,14 +326,18 @@ public string Phone public IList Attachments { get; private set; } = new List(); public List CompletedDocuments { get; set; } = new List(); - + + public List teamMembersInfo = new List(); + public List linksInfo = new List(); + public List linksURLInfo = new List(); + + [DisplayName("Are you the sponsor")] [Browsable(true)] public bool IsSponsor { get; set; } = true; [Export(true)] - [Required] [StringLength(255, ErrorMessage = "Sponsor first name is too long.")] [DisplayName("Sponsor First Name")] [Browsable(true)] @@ -256,7 +355,6 @@ public string SponsorFirstName } [Export(true)] - [Required] [StringLength(255, ErrorMessage = "Sponsor last name is too long.")] [DisplayName("Sponsor Last Name")] [Browsable(true)] @@ -274,7 +372,6 @@ public string SponsorLastName } [Export(true)] - [EmailAddress] [DisplayName("Sponsor Email")] [Browsable(true)] public string SponsorEmail @@ -291,7 +388,6 @@ public string SponsorEmail } [Export(true)] - [Phone] [DisplayName("Sponsor Phone")] [Browsable(true)] public string SponsorPhone @@ -324,6 +420,26 @@ public string Status } } + private string editProject = string.Empty; + + [Export(true)] + [DisplayName("Edit Project")] + // [Browsable(true)] + [ColumnHeader] + public string EditProject + { + get + { + return this.editProject; + } + set + { + this.editProject = value; + this.IsDirty = true; + } + } + + [Export(false)] [DisplayName("Comments:")] public Comments Comments { get; } = new Comments(); @@ -334,11 +450,13 @@ public string Status public void SetAttachments(IList files) { + /* if (files.Count == 0) { this.IsDirty = false; return; } + */ this.Attachments.Clear(); @@ -350,6 +468,68 @@ public void SetAttachments(IList files) this.IsDirty = true; } + public void SetLinks(List linksToAdd) + { + /* + if (linksToAdd.Count == 0) + { + this.IsDirty = false; + return; + } + */ + this.linksInfo.Clear(); + + foreach (var link in linksToAdd) + { + this.linksInfo.Add(link as String); + } + + this.IsDirty = true; + } + + public void SetLinksURL(List linkURLsToAdd) + { + /* + if (linksToAdd.Count == 0) + { + this.IsDirty = false; + return; + } + */ + this.linksURLInfo.Clear(); + + foreach (var linkURL in linkURLsToAdd) + { + this.linksURLInfo.Add(linkURL as String); + } + + this.IsDirty = true; + } + + + public void SetMembers(List membersToAdd) + { + /* + if (membersToAdd.Count == 0) + { + this.IsDirty = false; + return; + } + */ + + this.teamMembersInfo.Clear(); + + foreach (var member in membersToAdd) + { + this.teamMembersInfo.Add(member as String); + } + + this.IsDirty = true; + } + + + + public void AddAttachments(IList files) { if (files.Count == 0) @@ -381,5 +561,6 @@ public IEnumerable> GetExportableInformation() } } } + } } -} + diff --git a/CaPPMS/Pages/About.razor b/CaPPMS/Pages/About.razor index 9a1bd44..5bbfe80 100644 --- a/CaPPMS/Pages/About.razor +++ b/CaPPMS/Pages/About.razor @@ -1,5 +1,6 @@ @page "/about" +

About

diff --git a/CaPPMS/Pages/Faq.razor b/CaPPMS/Pages/Faq.razor index c72adf9..858251e 100644 --- a/CaPPMS/Pages/Faq.razor +++ b/CaPPMS/Pages/Faq.razor @@ -4,7 +4,6 @@ @page "/faq" -

FAQs

diff --git a/CaPPMS/Pages/FaqList.razor b/CaPPMS/Pages/FaqList.razor index 0352da7..638564a 100644 --- a/CaPPMS/Pages/FaqList.razor +++ b/CaPPMS/Pages/FaqList.razor @@ -1,43 +1,71 @@ @using CaPPMS.Data - @inject FaqManagerService FaqManager - + @page "/faqList" + + + +
+
+

Questions

+
-

FAQ List

- - -
- -
+ Currently the form only allows users to input a question and not a reply to other users questions because we do not want just anyone to be able to respond to the questions. + The questions need to be reviewed by the administrator and then answered in order to maintain accuracy and professionalism. +
+
+ + +
+ +
+ @if (_selectedFaq != null) {
-
-} - +} + @code { private FaqInformation _selectedFaq; private string _popOverTitle; diff --git a/CaPPMS/Pages/IndividualManageProject.razor b/CaPPMS/Pages/IndividualManageProject.razor new file mode 100644 index 0000000..26925fe --- /dev/null +++ b/CaPPMS/Pages/IndividualManageProject.razor @@ -0,0 +1,1476 @@ +@page "/individualManageProject/{ProjectID}" +@using CaPPMS.Model +@namespace CaPPMS.Shared +@inject ProjectManagerService ProjectManager +@inject NavigationManager NavigationManager +@* @inject GitHubService GitHub *@ +@using Microsoft.AspNetCore.WebUtilities +@using Microsoft.AspNetCore.Components.Forms +@using System.Collections.Generic +@inject IJSRuntime JSRuntime + + + + + +@foreach (ProjectInformation idea in sortedProjects) +{ +
+

@idea.ProjectTitle

+
+ +
Submitter Information
+
+
+ + @* *@ + @* *@ + +
+
+ + +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
Sponsor Information
+
+
+ + @* *@ + +
+
+ + +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
Project Information
+
+
+ + +
+
+ + +
+
+
+
+ @*
+ + + @foreach (var index in Enumerable.Range(0, teamMembers.Count)) + { + + + } + + +
+
+ +
+ *@ + + + +
+ +
+
+ +
+
+ @* *@ + + +
+
+
+
+ @* @foreach (var member in teamMembers) *@ + @foreach (var index in Enumerable.Range(0, teamMembers.Count)) + + { +
+
+ @* *@ + +
+
+ @* *@ + +
+
+
+ } +
+
+ +
+ + @*
+
+ @if (errorMessage) + { + + } +
+
*@ +
+ + +
+ +
+
+ +
+
+ +
+
+ @* *@ +
+
+
+
+
+
+ +
+
+ +
+
+ @* *@ +
+
+ +
+ + + @*
*@ +
+
+ + + + +
+ + + + @foreach (var index in Enumerable.Range(0, Math.Max(links.Count, linkUrl.Count))) + { + +
+
+ + + + + + } + + + + @* *@ +
+
+ +
+ + + +@* +
+ @foreach (var newUrl in newLinks.Select((value, index) => new { value, index })) + { +
+
+
+ +
+
+ +
+
+ +
+
+ + } +
+
+ +
+
+
+ @if (displayErrorMessage) + { + + } *@ + + + + @*
+ @foreach (var newUrl in newLinks) + { +
+
+ + TEST5 +
+
+ +
+
+ +
+
+ } +
*@ + + @*
+
+ +
+
+ +
+
+ +
+
*@ + + @*
+ @foreach (var newUrl in newLinks) + { +
+
+ +
+
+ +
+
+ +
+
+ } + +
+ + + @*
+
+ +
+
+ +
+
+ +
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+ +
+
+ +
+
+ +
+
+ *@ + + + + + + + +
+
+

+ + +
+ @foreach (ProjectFile file in attachmentSelection.Keys) + { + + @if (attachmentSelection[file]) + { + + @file.Name + + } + else + { + + @file.Name + + } + + @* + +
+ + +
+ + + +
+ + + +
+ +
+ +
+ +
+ +
+ + + *@ +

+ } +
+
+
+
+
+

Drag and drop documents here

+
+ @* + *@ +
+ @* @if (selectedFiles.Count > 0) + { +
+

Selected File(s):

+ @foreach (var file in selectedFiles) + { + @file.Name + } +
+ } *@ + +
+ +
+ @* *@ + @if(loadedFiles.Any()) + { +
+
+ Attached Files: +
    + @foreach (var file in loadedFiles) + { +
  • @file.Name + +
  • + } +
+
+
+ } + + + + + + + + @**@ +
+
+
+
+ + @*
Comments
+ @if (Comments.Any()) + { + foreach (var comment in Comments) + { +
+

@comment.Text

+ @comment.Timestamp.ToString("g") +
+ } + } + else + { +

This project has no comments

+ } + + + +
+ *@ + + +
+
+ + @if (idea.Status.Equals("Approved")) + { +
+ + + } + + @if (idea.Status.Equals("Complete")) + { + Project Completed @idea.SemesterTerm @idea.SemesterYear + } + + + + +

+
Actions
+
+ @if(idea.Status.Equals("New")) + { + + + } + + + + +
+
+
+
+
+
+ +
+ + + + + +
+ + + +
+ @if (index < links.Count) + { + + } + + @if (index < linkUrl.Count) + { + + } + + +
+ + @if (completeClicked) + { + populateCompletedProject(idea); + + } +
+
+
+
+
+
+
+
+
+} + +@code { + + [Parameter] + public string ProjectID { get; set; } + + IEnumerable sortedProjects; + public bool IsDirty { get; set; } = false; + + private void SetCompletedProjectsList() + { + Guid parsedProjectId; + bool isValidGuid = Guid.TryParse(ProjectID, out parsedProjectId); + + if (!isValidGuid) + { + sortedProjects = Enumerable.Empty(); + return; + } + + List completedProjects = new List(); + foreach (ProjectInformation projectInformation in ProjectManager.ProjectIdeas.Values) + { + if (projectInformation.ProjectID == parsedProjectId) + { + completedProjects.Add(projectInformation); + + _infoPlaceholder = new ProjectInformation(); + _infoPlaceholder.VideoLink = projectInformation.VideoLink; + _infoPlaceholder.ProjectTitle = projectInformation.ProjectTitle; + _infoPlaceholder.ProjectDescription = projectInformation.ProjectDescription; + _infoPlaceholder.TeamMember = projectInformation.TeamMember; + _infoPlaceholder.LinkName = projectInformation.LinkName; + _infoPlaceholder.Url = projectInformation.Url; + _infoPlaceholder.Github = projectInformation.Github; + _infoPlaceholder.FirstName = projectInformation.FirstName; + _infoPlaceholder.LastName = projectInformation.LastName; + _infoPlaceholder.Email = projectInformation.Email; + _infoPlaceholder.Phone = projectInformation.Phone; + _infoPlaceholder.SponsorFirstName = projectInformation.SponsorFirstName; + _infoPlaceholder.SponsorLastName = projectInformation.SponsorLastName; + _infoPlaceholder.SponsorEmail = projectInformation.SponsorEmail; + _infoPlaceholder.SponsorPhone = projectInformation.SponsorPhone; + _infoPlaceholder.teamMembersInfo = projectInformation.teamMembersInfo; + + foreach(var member in projectInformation.teamMembersInfo) + { + teamMembers.Add(member); + } + + foreach(var link in projectInformation.linksInfo) + { + links.Add(link); + } + + foreach (var linkURL in projectInformation.linksURLInfo) + { + linkUrl.Add(linkURL); + } + + //teamMembers = projectInformation.teamMembersInfo; + + + foreach (ProjectFile file in projectInformation.Attachments) + { + attachmentSelection.Add(file, false); + } + + + } + } + sortedProjects = completedProjects; + } + + private void populateCompletedProject(ProjectInformation submittedProject) + { + completedProject = new ProjectInformation(); + completedProject.ProjectID = submittedProject.ProjectID; + completedProject.VideoLink = submittedProject.VideoLink; + completedProject.ProjectTitle = submittedProject.ProjectTitle; + completedProject.ProjectDescription = submittedProject.ProjectDescription; + completedProject.TeamMember = submittedProject.TeamMember; + completedProject.LinkName = submittedProject.LinkName; + completedProject.Url = submittedProject.Url; + completedProject.Github = submittedProject.Github; + completedProject.FirstName = submittedProject.FirstName; + completedProject.LastName = submittedProject.LastName; + completedProject.Email = submittedProject.Email; + completedProject.Phone = submittedProject.Phone; + completedProject.SponsorFirstName = submittedProject.SponsorFirstName; + completedProject.SponsorLastName = submittedProject.SponsorLastName; + completedProject.SponsorEmail = submittedProject.SponsorEmail; + completedProject.SponsorPhone = submittedProject.SponsorPhone; + completedProject.teamMembersInfo = submittedProject.teamMembersInfo; + + + foreach (ProjectFile file in submittedProject.Attachments) + { + completedProject.Attachments.Add(file); + } + + } + private ProjectInformation _infoPlaceholder; + private ProjectInformation completedProject; + + + private void MarkAsDirty(ProjectInformation project) + { + project.IsDirty = true; + } + + public string GetVideoEmbedUrl(string videoLink) + { + if (string.IsNullOrWhiteSpace(videoLink)) + { + return null; + } + + try + { + var uri = new Uri(videoLink); + var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query); + var videoId = query["v"].ToString(); + return $"https://www.youtube.com/embed/{videoId}"; + } + catch + { + return null; + } + } + + private bool selectAllChecked = false; + + private void ToggleSelectAll() + { + selectAllChecked = !selectAllChecked; + @foreach (ProjectFile file in attachmentSelection.Keys) + { + if (selectAllChecked) + { + attachmentSelection[file] = true; + + } + else + { + attachmentSelection[file] = false; + } + + } + + + } + + + public void UpdateProject(ProjectInformation updatedProject) + { + if (ProjectManager.ProjectIdeas.TryGetValue(updatedProject.ProjectID, out ProjectInformation project)) + { + project.FirstName = updatedProject.FirstName; + StateHasChanged(); + } + } + + private void OnDragOver(DragEventArgs e) + { + + } + + private void OnDrop(DragEventArgs e) + { + // Handle the drop event. Note that you might not be able to access the dropped files here directly in Blazor. This event is mainly for UI feedback, like changing the drop area's appearance. + } + + + private List selectedFiles = new List(); + + private void HandleSelectedFiles(InputFileChangeEventArgs e) + { + selectedFiles.Clear(); + foreach (var file in e.GetMultipleFiles()) + { + selectedFiles.Add(file); + } + StateHasChanged(); + } + + private ProjectInformation idea = new ProjectInformation(); + + public enum FormButtons + { + Submit, + Approver + } + + public static bool IsUploading + { + get + { + return isUploading; + } + } + + [Parameter] + public bool ShowFileAttachments { get; set; } = false; + + [Parameter] + public bool ShowComments { get; set; } = false; + + [Parameter] + public FormButtons ShowFormButton { get; set; } = FormButtons.Submit; + + [Parameter] + public IIdea Parent { get; set; } = null; + + [Parameter] + public ProjectInformation ProjectIdea + { + get + { + return idea; + } + set + { + idea = value; + } + } + + [CascadingParameter] + private Task authenticationState { get; set; } + + private string DownloadFileName => $"{idea.ProjectTitle}-Export.pdf"; + + private string Error { get; set; } + + private List loadedFiles = new(); + + private Dictionary attachmentSelection = new Dictionary(); + + private static bool isUploading = false; + + private bool completeClicked = false; + + private string tableState = "block"; + + private string waitBar => tableState.Equals("none") ? "visible" : "hidden" ; + + protected override void OnInitialized() + { + SetCompletedProjectsList(); + base.OnInitialized(); + + if (this.ShowFormButton == FormButtons.Approver) + { + this.idea.IsDirty = false; + } + } + + private async Task HandleValidSubmit() + { + string link = ""; + if (!idea.Url.StartsWith("http://") && !idea.Url.StartsWith("https://") && idea.Url.Length != 0) + { + link = "https://" + idea.Url; + idea.Url = link; + } + + if (!idea.Github.StartsWith("http://") && !idea.Github.StartsWith("https://") && idea.Github.Length != 0) + { + link = "https://" + idea.Github; + idea.Github = link; + } + + if (!idea.VideoLink.StartsWith("http://") && !idea.VideoLink.StartsWith("https://") && idea.VideoLink.Length != 0) + { + link = "https://" + idea.VideoLink; + idea.VideoLink = link; + } + + idea.TeamMember = initialValue + " , " + string.Join(", ", teamMembers.Where(tm => !string.IsNullOrWhiteSpace(tm))); + + idea.LinkName = initialValueLinkName + " , "+ string.Join(", ", links.Where(tm => !string.IsNullOrWhiteSpace(tm))); + + idea.Url = initialValueUrl + " , " + string.Join(", ", linkUrl.Where(tm => !string.IsNullOrWhiteSpace(tm))); + + + this.tableState = "none"; + + await Task.Run( async () => + { + var uploaded = await ProjectManager.AddAsync(idea); + + if (uploaded) + { + idea.IsDirty = false; + teamMembers.Clear(); + loadedFiles.Clear(); + links.Clear(); + linkUrl.Clear(); + idea = new ProjectInformation(); + + } + + await Task.Delay(2000); + }); + + this.tableState = "block"; + } + + @* private void LoadFiles(InputFileChangeEventArgs inputFile) + { + this.loadedFiles.Clear(); + foreach (var file in inputFile.GetMultipleFiles().Take(ProjectManager.MaxNumberOfFiles)) + { + if (file.Size > ProjectManager.MaxMBSizePerFile) + { + continue; + } + + var projectFile = new ProjectFile(file); + + this.loadedFiles.Add(projectFile); + } + + idea.SetAttachments(loadedFiles); + } *@ + + private int uploadProgress = 0; + + + private async void LoadFiles(InputFileChangeEventArgs inputFile) + { + foreach (var updatedProject in sortedProjects) + { + if (ProjectManager.ProjectIdeas.TryGetValue(updatedProject.ProjectID, out ProjectInformation existingProject)) + { + var newFiles = inputFile.GetMultipleFiles() + .Take(ProjectManager.MaxNumberOfFiles) + .Where(file => file.Size <= ProjectManager.MaxMBSizePerFile) + .Select(file => new ProjectFile(file)); + + this.loadedFiles = this.loadedFiles.Concat(newFiles).ToList(); + + @* this.loadedFiles = this.loadedFiles.Where(file => !ShouldRemoveFile(file)).ToList(); *@ + + + var totalFiles = loadedFiles.Count; + var iterations = 10; + + for (var i = 0; i < iterations; i++) + { + var progress = (int)(((float)i / (iterations - 1)) * 100); + uploadProgress = progress; + StateHasChanged(); + + await Task.Delay(500); + } + + + + updatedProject.AddAttachments(loadedFiles); + + + + uploadProgress = 0; + await Task.Yield(); + StateHasChanged(); + } + } + + + + + + } + + // Add this method to remove files based on their filenames + private void RemoveFileByName(string fileName) + { + this.loadedFiles = this.loadedFiles.Where(file => file.Name != fileName).ToList(); + // Update attachments after removing the file + idea.SetAttachments(loadedFiles); + } + + private void SponsorCheckBox_Changed(EventArgs e) + { + this.idea.IsSponsor = !this.idea.IsSponsor; + + if (!this.idea.IsSponsor) + { + ResetSponsor(); + } + } + + private void SponsorCheck(EventArgs e) + { + if (idea.IsSponsor) + { + idea.SponsorFirstName = idea.FirstName; + idea.SponsorLastName = idea.LastName; + idea.SponsorEmail = idea.Email; + idea.SponsorPhone = idea.Phone; + } + } + + private void ResetSponsor() + { + idea.SponsorFirstName = string.Empty; + idea.SponsorLastName = string.Empty; + idea.SponsorEmail = string.Empty; + idea.SponsorPhone = string.Empty; + } + + private async Task UpdateInfo(bool confirmed) + { + + if (!confirmed) + { + return; + } + + bool anyUpdates = false; + + foreach (var updatedProject in sortedProjects) + { + //if (updatedProject.IsDirty) + //{ + if (ProjectManager.ProjectIdeas.TryGetValue(updatedProject.ProjectID, out ProjectInformation existingProject)) + { + anyUpdates = true; + updatedProject.ProjectDescription = _infoPlaceholder.ProjectDescription; + updatedProject.ProjectTitle = _infoPlaceholder.ProjectTitle; + updatedProject.FirstName = _infoPlaceholder.FirstName; + updatedProject.LastName = _infoPlaceholder.LastName; + updatedProject.TeamMember = _infoPlaceholder.TeamMember; + + updatedProject.Email = _infoPlaceholder.Email; + updatedProject.Phone = _infoPlaceholder.Phone; + updatedProject.SponsorFirstName = _infoPlaceholder.SponsorFirstName; + updatedProject.SponsorLastName = _infoPlaceholder.SponsorLastName; + updatedProject.SponsorEmail = _infoPlaceholder.SponsorEmail; + updatedProject.SponsorPhone = _infoPlaceholder.SponsorPhone; + updatedProject.Github = _infoPlaceholder.Github; + updatedProject.VideoLink = _infoPlaceholder.VideoLink; + updatedProject.LinkName = _infoPlaceholder.LinkName; + updatedProject.Url = _infoPlaceholder.Url; + updatedProject.SetMembers(teamMembers); + updatedProject.SetLinks(links); + updatedProject.SetLinksURL(linkUrl); + await Task.Run(async () => + { + var uploaded = await ProjectManager.UpdateAsync(updatedProject); + + if (uploaded) + { + updatedProject.IsDirty = false; + } +} ); + + + } + //} + } + if (anyUpdates) + { + ProjectManager.NotifyProjectIdeasChanged(); + } + StateHasChanged(); + } + + private async Task UpdateRecordAsync(EventArgs e, ProjectInformation approvedProject) + { + if (!approvedProject.IsDirty) + { + return; + } + + approvedProject.IsDirty = !await ProjectManager.UpdateAsync(approvedProject); + } + + + + private async Task ApproveIdea(MouseEventArgs e) + { + foreach (var updatedProject in sortedProjects) + { + if (ProjectManager.ProjectIdeas.TryGetValue(updatedProject.ProjectID, out ProjectInformation existingProject)) + { + + updatedProject.Status = "Approved"; + + await UpdateRecordAsync(e, updatedProject); + } + } + + } + + private async Task ExportIdea(MouseEventArgs e) + { + foreach (var updatedProject in sortedProjects) + { + if (ProjectManager.ProjectIdeas.TryGetValue(updatedProject.ProjectID, out ProjectInformation existingProject)) + { + + var location = await this.ProjectManager.ExportAsync(updatedProject); + + NavigationManager.NavigateTo($"{ProjectManager.FileManager.DownloadPath}{location}", true); + + } + } + + + @* this.CloseForm(EventArgs.Empty); *@ + } + + private async Task DeleteIdea(bool confirmed) + { + + if (!confirmed) + { + return; + } + + foreach (var updatedProject in sortedProjects) + { + if (ProjectManager.ProjectIdeas.TryGetValue(updatedProject.ProjectID, out ProjectInformation existingProject)) + { + var authState = await authenticationState; + string error = await this.ProjectManager.DeleteAsync(updatedProject, authState.User); + + if (string.IsNullOrEmpty(error)) + { + NavigationManager.NavigateTo($"/projectlist", true); + + @* this.CloseForm(EventArgs.Empty); *@ + } + else + { + this.Error = error; + } + + } + } + + } + + + private async Task DeleteSelectedAttachments(bool confirmed) + { + if (!confirmed) + { + return; + } + + var authState = await authenticationState; + + foreach (var updatedProject in sortedProjects) + { + if (ProjectManager.ProjectIdeas.TryGetValue(updatedProject.ProjectID, out ProjectInformation existingProject)) + { + foreach (ProjectFile file in attachmentSelection.Keys) + { + Console.WriteLine("IS THE FILE SELECTED: " + attachmentSelection[file]); + if (attachmentSelection[file]) + { + await ProjectManager.RemoveFileAsync(updatedProject, file, authState.User); + } + } + + } + } + + } + + + private void DownloadFile(IProjectFile file) + { + NavigationManager.NavigateTo($"/download/{file.Location}", true); + } + + private bool IsExpanded { get; set; } = false; + private void ToggleExpandCollapse() + { + IsExpanded = !IsExpanded; + } + + // Define a list to store team members + List teamMembers = new List(); + + // Function to add a new team member input field + private void AddTeamMember() + { + // Add an empty string to the list + teamMembers.Add(""); + + } + String initialValue = ""; + private void setInitialTeamMemberValue(string value) + { + initialValue = value; + } + + String initialValueLinkName = ""; + private void setInitialLinkNameValue(string value) + { + initialValueLinkName = value; + } + + String initialValueUrl = ""; + private void setInitialUrlValue(string value) + { + initialValueUrl = value; + } + + private void UpdateIdeaTeamMembers(int index, string value) + { + teamMembers[index] = value; // Update the teamMembers array + + // Update idea's TeamMembers property + // idea.TeamMember = initialValue + " , " + string.Join(", ", teamMembers.Where(tm => !string.IsNullOrWhiteSpace(tm))); + } + + // Function to remove the last added team member input field + private void RemoveLastTeamMember() + { + // Check if there are any team members in the list + if (teamMembers.Count > 0) + { + // Remove the last added team member (which is at the end of the list) + teamMembers.RemoveAt(teamMembers.Count - 1); + + // Update idea's TeamMembers property + // idea.TeamMember = string.Join(", ", teamMembers.Where(tm => !string.IsNullOrWhiteSpace(tm))); + } +} + + @* List teamMembers = new List(); + List links = new List(); + bool errorMessage = false; + + //AddTeamMember limit set to 8 + private void AddTeamMember() + { + if (teamMembers.Count < 100) + { + teamMembers.Add(""); + errorMessage = false; + } + else + { + errorMessage = true; + } + } + + void UpdateTeamMember(ChangeEventArgs e, string member) + { + var index = teamMembers.IndexOf(member); + if(index != -1) + { + teamMembers[index] = e.Value.ToString(); + } + } + + void RemoveTeamMember(string member) + { + teamMembers.Remove(member); + } *@ + + @* // Function to add a new link input field + private void AddLink() + { + links.Add(""); + } + *@ + + string NewCommentText { get; set; } + List Comments = new List(); + + void AddComment() + { + if (!string.IsNullOrWhiteSpace(NewCommentText)) + { + var newComment = new Comment + { + Text = NewCommentText, + Timestamp = DateTime.Now + }; + Comments.Add(newComment); + NewCommentText = string.Empty; + } + } + + public class Comment + { + public string Text { get; set; } + public DateTime Timestamp { get; set; } + } + + private string GenerateDownloadLinkForDoc1() + { + return "/download/document1.pdf"; + } + + private bool isDocSelected=false; + + private void ToggleDocSelected() + { + if(isDocSelected) + { + isDocSelected=false; + } + else + { + isDocSelected=true; + } + } + + private void DownloadSelected() + { + if (isDocSelected) + { + var downloadLink = GenerateDownloadLinkForDoc1(); + DownloadFile(downloadLink); + } + } + + private async Task DownloadFile(string url) + { + await JSRuntime.InvokeVoidAsync("downloadFile", url); + } + + @* private string currentInput = "VideoLink"; + private List inputs = new List { "VideoLink", "Github", "Url" }; + + private string currentInputLinks = "VideoLink"; + private List inputSequence = new List { "VideoLink", "Github", "Url" }; + + private void NextInput() + { + int currentIndex = inputSequence.IndexOf(currentInputLinks); + int nextIndex = (currentIndex + 1) % inputSequence.Count; // Use modulo to cycle back to 0 + currentInputLinks = inputSequence[nextIndex]; + } *@ + + //private List linkInputs = new(); + private List LinkInputModel = new List(); + + @* private void AddInput() + { + // Add two new instances of LinkInputModel to the list + linkInputs.Add(new LinkInputModel()); + linkInputs.Add(new LinkInputModel()); + } + + private void RemoveInput(LinkInputModel linkInput) + { + // Remove the specified LinkInputModel instance from the list + if (linkInputs.Count > 1) // Prevent removing all input fields + { + linkInputs.Remove(linkInput); + } + } + *@ + + + @* List newLinks = new List(); + bool displayErrorMessage = false; + + private void AddNewLink() + { + if (newLinks.Count < 100) + { + newLinks.Add(""); + displayErrorMessage = false; + } + else + { + displayErrorMessage = true; + } + } + + void RemoveLink(int index) + { + if (index >= 0 && index < newLinks.Count) + { + newLinks.RemoveAt(index); + } + } *@ + + List links = new List(); + // Function to add a new link input field + private void AddLink() + { + // Add an empty string to the list + links.Add(""); + } + + private void UpdateIdeaLinkName(int index, string value) + { + links[index] = value; // Update the teamMembers array + + // Update idea's TeamMembers property + @* idea.LinkName = string.Join(", ", links.Where(tm => !string.IsNullOrWhiteSpace(tm))); *@ + } + + List linkUrl = new List(); + private void AddUrlLink() + { + linkUrl.Add(""); + } + + public void AddLinkandUrl() +{ + // Add a new link and URL entry + links.Add(""); // Add an empty string for the new link name + linkUrl.Add(""); // Add an empty string for the new link URL +} + +public void RemoveLinkandURL() +{ + RemoveLastLink(); + RemoveLastUrlLink(); +} + + + + private void UpdateIdeaUrl(int index, string value) + { + linkUrl[index] = value; // Update the teamMembers array + + // Update idea's TeamMembers property + @* idea.Url = string.Join(", ", linkUrl.Where(tm => !string.IsNullOrWhiteSpace(tm))); *@ + } + + + // Function to remove the last added link input field +private void RemoveLastLink() +{ + // Check if there are any links in the list + if (links.Count > 0) + { + // Remove the last added link (which is at the end of the list) + links.RemoveAt(links.Count - 1); + + @* idea.LinkNames = string.Join(", ", links.Where(tm => !string.IsNullOrWhiteSpace(tm))); *@ + } +} + +// Function to remove the last added URL link input field +private void RemoveLastUrlLink() +{ + // Check if there are any URL links in the list + if (linkUrl.Count > 0) + { + // Remove the last added URL link (which is at the end of the list) + linkUrl.RemoveAt(linkUrl.Count - 1); + + @* idea.Url = string.Join(", ", linkUrl.Where(tm => !string.IsNullOrWhiteSpace(tm))); *@ + } +} + + +} + + diff --git a/CaPPMS/Pages/IndividualProject.razor b/CaPPMS/Pages/IndividualProject.razor new file mode 100644 index 0000000..03fe2c4 --- /dev/null +++ b/CaPPMS/Pages/IndividualProject.razor @@ -0,0 +1,303 @@ +@using CaPPMS.Model +@using Microsoft.AspNetCore.WebUtilities + +@* @page "/individualProject" *@ +@page "/individualProject/{ProjectID}" + + +@* @inject ProjectManagerService ProjectManager +@foreach (ProjectInformation idea in sortedProjects) +{ + termStr = idea.SemesterTerm + idea.SemesterYear; + if (!activeTerms.Contains(termStr)) + { +

@idea.SemesterTerm @idea.SemesterYear

+ activeTerms.Add(termStr); + } +
@idea.ProjectTitle
+

@idea.ProjectDescription

+ if (idea.Github.Length > 0) + { + Project GitHub Link: + @idea.Github +
+ } +
+ @if (idea.ProjectID != Guid.Empty) + { + Project ID: +

@idea.ProjectID

+ } + if (idea.Url.Length > 0) + { + Project Website Link: + @idea.Url +
+ } + + if (idea.CompletedDocuments.Count > 0) + { + @foreach (CompletedProjectDocumentation proj in idea.CompletedDocuments) + { + Team: +

@proj.TeamName

+ + if (proj.GitHubLink.Length > 0) + { + Team GitHub Link: + @proj.GitHubLink +
+ } + if (proj.VideoLink.Length > 0) + { + Team Video Link: + @proj.VideoLink +
+ } + if (proj.WebsiteLink.Length > 0) + { + Team Website Link: + @proj.WebsiteLink +
+ } + + Documents for Team @proj.TeamName: + + + } + + } + +
+} +
+ +@code { + + [Parameter] + public string ProjectID { get; set; } + + IEnumerable sortedProjects; + protected override void OnInitialized() + { + SetCompletedProjectsList(); + ProjectManagerService.ProjectIdeasChanged += (o, e) => + { + SetCompletedProjectsList(); + }; + base.OnInitialized(); + } + private List activeTerms = new List(); + private string termStr; + private void SetCompletedProjectsList() + { + List completedProjects = new List(); + foreach (ProjectInformation projectInformation in ProjectManager.ProjectIdeas.Values) + { + if (projectInformation.Status.Equals("Complete")) + { + completedProjects.Add(projectInformation); + } + } + sortedProjects = completedProjects + .OrderByDescending(o => o.SemesterYear) + .ThenBy(o => o.SemesterTerm == "Spring") + .ThenBy(o => o.SemesterTerm == "Summer") + .ThenBy(o => o.SemesterTerm == "Fall"); + } +} *@ + + + +@inject ProjectManagerService ProjectManager +@foreach (ProjectInformation idea in sortedProjects) +{ +
+

@idea.ProjectTitle

+
+
+ @if (!string.IsNullOrEmpty(GetVideoEmbedUrl(idea.VideoLink))) + { + + } + else + { +

+ + } +
+
+ @idea.ProjectDescription +
+
+ if (idea.CompletedDocuments.Count > 0) + { + @foreach (CompletedProjectDocumentation proj in idea.CompletedDocuments) + { + Team Name: @proj.TeamName + @*

@proj.TeamName

*@ +
+
+ Documents for Team @proj.TeamName: +
+
+ + + } + } + @*
+
+ Project ID: @idea.ProjectID +
+
+ Semester Term: @idea.SemesterTerm +
+
+ Semester Year: @idea.SemesterYear *@ + @* Links: +
+
*@ + + Links: +
+
+
    + @if (!string.IsNullOrWhiteSpace(idea.Github)) + { +
  • GitHub
  • + } + else + { + + + } + @if (!string.IsNullOrWhiteSpace(idea.LinkName)) + { +
  • @idea.LinkName
  • + } + @foreach (var index in Enumerable.Range(0, Math.Max(idea.linksInfo.Count, idea.linksURLInfo.Count))) + { + @if (!string.IsNullOrWhiteSpace(idea.linksURLInfo[index])) + { +
  • @idea.linksInfo[index]
  • + } + } + + +
+ + + + @*if (!string.IsNullOrWhiteSpace(idea.Github)) + { + @*
  • GitHub
  • + + Links: +
    +
    + + } + else + { + + + }*@ + + @* Team Name: *@ + Team Members: +
    +
    + +
    +
    +
    +
    + +} + + + +@code { + [Parameter] + public string ProjectID { get; set; } + + IEnumerable sortedProjects; + + protected override void OnInitialized() + { + SetCompletedProjectsList(); + base.OnInitialized(); + } + + private void SetCompletedProjectsList() + { + Guid parsedProjectId; + bool isValidGuid = Guid.TryParse(ProjectID, out parsedProjectId); + + if (!isValidGuid) + { + sortedProjects = Enumerable.Empty(); + return; + } + + List completedProjects = new List(); + foreach (ProjectInformation projectInformation in ProjectManager.ProjectIdeas.Values) + { + if (projectInformation.ProjectID == parsedProjectId) + { + completedProjects.Add(projectInformation); + } + } + sortedProjects = completedProjects; + } + public string GetVideoEmbedUrl(string videoLink) + { + if (string.IsNullOrWhiteSpace(videoLink)) + { + return null; + } + + try + { + var uri = new Uri(videoLink); + var query = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query); + var videoId = query["v"].ToString(); + return $"https://www.youtube.com/embed/{videoId}"; + } + catch + { + return null; + } + } +} diff --git a/CaPPMS/Pages/PreviousProjects.razor b/CaPPMS/Pages/PreviousProjects.razor index d64ad4b..4b97e48 100644 --- a/CaPPMS/Pages/PreviousProjects.razor +++ b/CaPPMS/Pages/PreviousProjects.razor @@ -1,11 +1,17 @@ @page "/previousprojects" -

    Previous Projects

    +
    +

    Manage Projects

    - The documents and products created by students from each semester can be found here. + The Master of Information Systems with a Specialization in Software + Engineering program at the University of Maryland Global Campus ends + with a capstone course. This webpage allows authorized users to manage + projects by editing data, uploading/deleting files, and making comments.

    -
    + + +@*
    @inject ProjectManagerService ProjectManager @foreach (ProjectInformation idea in sortedProjects) @@ -110,4 +116,21 @@ .ThenBy(o => o.SemesterTerm == "Summer") .ThenBy(o => o.SemesterTerm == "Fall"); } +} *@ + +@using CaPPMS.Data +@using CaPPMS.Model +@inject ProjectManagerService ProjectManager + + + + @* + + *@ + + + + + +@code { } diff --git a/CaPPMS/Pages/ProjectList.razor b/CaPPMS/Pages/ProjectList.razor index feec4c1..ccdba1c 100644 --- a/CaPPMS/Pages/ProjectList.razor +++ b/CaPPMS/Pages/ProjectList.razor @@ -3,10 +3,25 @@ @inject ProjectManagerService ProjectManager @page "/projectlist" +
    +

    Projects

    +

    + The project page on the website allows users to explore a diverse range of real-world projects undertaken by + students + in the Master of Information Systems program with a specialization in Software Engineering at the University of + Maryland Global Campus. + These projects often represent the culmination of students' academic endeavors, showcasing their ability to apply + theoretical knowledge + and practical skills to solve complex problems in the field. By browsing through past projects, prospective students + can gain valuable insights + into the innovative solutions developed by graduates and the tangible outcomes of the program's curriculum.. +

    - - - + + + @code { } diff --git a/CaPPMS/Pages/_Host.cshtml b/CaPPMS/Pages/_Host.cshtml index bca883e..7e39a8d 100644 --- a/CaPPMS/Pages/_Host.cshtml +++ b/CaPPMS/Pages/_Host.cshtml @@ -38,5 +38,12 @@
    + + + diff --git a/CaPPMS/SQL_Creation/CaPPMS_Table_Generation.sql b/CaPPMS/SQL_Creation/CaPPMS_Table_Generation.sql index 6bce2e0..97ee417 100644 --- a/CaPPMS/SQL_Creation/CaPPMS_Table_Generation.sql +++ b/CaPPMS/SQL_Creation/CaPPMS_Table_Generation.sql @@ -2,7 +2,8 @@ CREATE TABLE [dbo].[FAQ] ( [FAQ_ID] INT NOT NULL PRIMARY KEY, [FAQ_Question] NVARCHAR(50) NOT NULL, - [FAQ_Answer] NVARCHAR(MAX) NULL + [FAQ_Answer] NVARCHAR(MAX) NULL, + [Author] NVARCHAR(MAX) NULL ) CREATE TABLE [dbo].[Contact] @@ -54,6 +55,36 @@ CREATE TABLE [dbo].[Project_Idea] CONSTRAINT [FK_Project_Idea_Contact] FOREIGN KEY ([Sponsor_ID]) REFERENCES [Contact]([Contact_ID]) ) +CREATE TABLE [dbo].[Zip] +( + [Zip_Id] BIGINT NOT NULL PRIMARY KEY, + [Zip_Name] NVARCHAR(50) NULL, + [Zip_Path] NVARCHAR(MAX) NOT NULL, + [Zip_Size] NVARCHAR(MAX) NULL, + [Zip_Description] BIGINT NULL, + [P_ID] BIGINT NOT NULL, + [File_ID] BIGINT NOT NULL, + CONSTRAINT [FK_Zip_Project_Information] FOREIGN KEY ([P_ID]) REFERENCES [Project_Information]([P_ID]), + CONSTRAINT [FK_Zip_Attachment] FOREIGN KEY ([File_ID]) REFERENCES [Attachment]([File_ID]) +) + +CREATE TABLE [dbo].[Link] +( + [Link_Id] BIGINT NOT NULL PRIMARY KEY, + [Link_URL] NVARCHAR(MAX) NOT NULL, + [P_ID] BIGINT NOT NULL, + CONSTRAINT [FK_Link_Project_Information] FOREIGN KEY ([P_ID]) REFERENCES [Project_Information]([P_ID]) +) + + +CREATE TABLE [dbo].[Team_Members] +( + [TM_Id] BIGINT NOT NULL PRIMARY KEY, + [Name] NVARCHAR(50) NULL, + [P_ID] BIGINT NOT NULL, + CONSTRAINT [FK_Team_members_Project_Information] FOREIGN KEY ([P_ID]) REFERENCES [Project_Information]([P_ID]) +) + GO CREATE INDEX [Index_Project_Title] ON [dbo].[Project_Information] ([Project_Title]) diff --git a/CaPPMS/Shared/CommentEntry.razor b/CaPPMS/Shared/CommentEntry.razor index e483eb9..c68d2f2 100644 --- a/CaPPMS/Shared/CommentEntry.razor +++ b/CaPPMS/Shared/CommentEntry.razor @@ -3,21 +3,26 @@ @inject ProjectManagerService ProjectManager
    -
    +
    -
    Author:@(this.IsAuthenticated ? comment.UserEmail : new String('*', 10))
    +
    Author: @(this.IsAuthenticated ? comment.UserEmail : new String('*', 10))
    + +
    +
    + @comment.Comments
    @if (this.IsAuthenticated) { }
    +
    -
    - @comment.Comments -
    +
    diff --git a/CaPPMS/Shared/CommentsComponent.razor b/CaPPMS/Shared/CommentsComponent.razor index 5fafcdf..819c633 100644 --- a/CaPPMS/Shared/CommentsComponent.razor +++ b/CaPPMS/Shared/CommentsComponent.razor @@ -6,7 +6,7 @@ @inject ProjectManagerService ProjectManager -

    Comments:

    +
    Comments
    @if(Comments != null && Comments.Count() > 0) { @@ -20,25 +20,20 @@ else {

    - No Comments Found + This project has no comments

    } -
    +
    @if (this.isAuthenticated) { -
    -
    - Add Comment: -
    -
    +
    Comment:
    -
    } @@ -107,7 +102,7 @@ else { ShouldUpdateState(); }; - + var authState = await authenticationStateTask; this.isAuthenticated = authState.User?.Identity.IsAuthenticated ?? false; Comments = CommentManager.GetComments(ProjectGuid); diff --git a/CaPPMS/Shared/CompleteProjectComponent.razor b/CaPPMS/Shared/CompleteProjectComponent.razor index 69e7ec5..f51d76d 100644 --- a/CaPPMS/Shared/CompleteProjectComponent.razor +++ b/CaPPMS/Shared/CompleteProjectComponent.razor @@ -6,7 +6,7 @@ -
    +

    @@ -272,7 +272,13 @@ ProjectInformation.CompletedDocuments.Add(completedProject); IsOpen = false; + + + await ProjectManager.UpdateAsync(ProjectInformation); + + await IsOpenChanged.InvokeAsync(IsOpen); + } private string GetFilePath(string fileName) diff --git a/CaPPMS/Shared/ConfirmationDialogBox.razor b/CaPPMS/Shared/ConfirmationDialogBox.razor index 4e64399..d0d851a 100644 --- a/CaPPMS/Shared/ConfirmationDialogBox.razor +++ b/CaPPMS/Shared/ConfirmationDialogBox.razor @@ -1,4 +1,4 @@ - + @if (Show) { @@ -9,10 +9,10 @@
    - +
    - +
    @@ -22,6 +22,7 @@ @code { public bool Show { get; set; } [Parameter] public string ButtonClass { get; set; } = ""; + [Parameter] public string ButtonStyle { get; set; } = ""; [Parameter] public string Command { get; set; } = "Action"; [Parameter] public string Prompt { get; set; } = "Are you sure?"; [Parameter] public EventCallback ConfirmedChanged { get; set; } diff --git a/CaPPMS/Shared/FaqModify.razor b/CaPPMS/Shared/FaqModify.razor index 1617bb0..a748574 100644 --- a/CaPPMS/Shared/FaqModify.razor +++ b/CaPPMS/Shared/FaqModify.razor @@ -1,5 +1,6 @@ @inject FaqManagerService FaqManager +
    @@ -26,7 +27,7 @@ @@ -34,19 +35,22 @@ -
    diff --git a/CaPPMS/Shared/FaqQuestion.razor b/CaPPMS/Shared/FaqQuestion.razor index 3f7528b..7908a30 100644 --- a/CaPPMS/Shared/FaqQuestion.razor +++ b/CaPPMS/Shared/FaqQuestion.razor @@ -1,25 +1,30 @@ @using CaPPMS.Model @namespace CaPPMS.Shared - +
    -
    +
    @if (IsAuthenticated) { }
     @FaqInformation.Question
    +
    @@ -27,6 +32,7 @@
    +
    @code { diff --git a/CaPPMS/Shared/FaqQuestionSubmission.razor b/CaPPMS/Shared/FaqQuestionSubmission.razor index c75d336..4b38fd1 100644 --- a/CaPPMS/Shared/FaqQuestionSubmission.razor +++ b/CaPPMS/Shared/FaqQuestionSubmission.razor @@ -1,5 +1,6 @@ @inject FaqManagerService FaqManager - +@inject IMailService MailService + @if (!isAuthenticated) {

    @@ -8,17 +9,18 @@
    - Add Topic: + Topic:
    - Add Question: - Question: +
    - Add Email: + Email:
    @@ -63,7 +65,22 @@ NewFaq = new(); isValidQuestion = false; + Email("rwilson@umgc.dev", "New Question submitted", "A new Question has been submitted. Please visit the CaPPMS website to approve or reject the Question."); + } + + private async Task Email(string email, string body, string html) + { + Console.WriteLine("REACHED SENDING EMAIL"); + try + { + await MailService.SendEmailAsync(email, body, html); + } + catch (Exception ex) + { + Console.WriteLine($"Error sending email: {ex.Message}"); + } } + private void AssignNewFaqQuestion(string value) { NewFaq.Question = value; diff --git a/CaPPMS/Shared/FaqReplySubmission.razor b/CaPPMS/Shared/FaqReplySubmission.razor index d7adcd8..61357de 100644 --- a/CaPPMS/Shared/FaqReplySubmission.razor +++ b/CaPPMS/Shared/FaqReplySubmission.razor @@ -15,7 +15,7 @@
    diff --git a/CaPPMS/Shared/FaqTopics.razor b/CaPPMS/Shared/FaqTopics.razor index 782600d..3dc5601 100644 --- a/CaPPMS/Shared/FaqTopics.razor +++ b/CaPPMS/Shared/FaqTopics.razor @@ -1,16 +1,20 @@ @using CaPPMS.Model @inject FaqManagerService FaqManager @namespace CaPPMS.Shared - +
    @foreach (string topic in this.faqTopicCollapseMap.Keys) { if (IsTopicWithAnsweredQuestions(topic) && IsTopicContainsFilter(topic)) {
    -
    +
    @@ -30,8 +34,9 @@ } } +
    } - +
    }
    diff --git a/CaPPMS/Shared/Idea.razor b/CaPPMS/Shared/Idea.razor index c441839..6c91b28 100644 --- a/CaPPMS/Shared/Idea.razor +++ b/CaPPMS/Shared/Idea.razor @@ -1,151 +1,292 @@ @inject ProjectManagerService ProjectManager @inject NavigationManager NavigationManager +@inject IMailService MailService +@inject IJSRuntime JSRuntime -
    + + + +
    + + + - - - - + + + + -
    - + + + - - - - - + - - - - - + - - - - - + + + @* *@ + + @foreach (var index in Enumerable.Range(0, teamMembers.Count)) { - + + } - } - else - { - - } + + +
    + +
    - - - - - - +
    +
    + @* *@ + + + + @* *@ + + + + @* + @foreach (var index in Enumerable.Range(0, links.Count)) + { + + + } + + @foreach (var index in Enumerable.Range(0, linkUrl.Count)) + { + + + } *@ + + @foreach (var index in Enumerable.Range(0, Math.Max(links.Count, linkUrl.Count))) + { + +
    +
    + + + + + } + + +
    + +
    + + + + + + @if (this.ShowFormButton == FormButtons.Submit) { - - + + }
    + +
    - + + +
    - +
    + +
    - + + +
    - +
    + +
    - + + +
    - @if (this.ShowFileAttachments) - { - Attachments: - @foreach (var file in this.idea.Attachments) + +
    + +
    + + + + + + + +
    - -
    - + + + +
    + + + + + + + + + + + + + + + +
    + @if (index < links.Count) + { + + } + + @if (index < linkUrl.Count) + { + + } + + +
    +
    - + @if (!idea.IsSponsor) { -

    Sponsor Information...

    - - - + + *@ + @* +
    + *@ + + + + +
    + @if (this.ShowFileAttachments) + { + Attachments + @foreach (var file in this.idea.Attachments) + { + + } + } + else + { +

    Drag and drop documents here

    +
    + @if (this.ShowFormButton == FormButtons.Submit) { - + + + } else if (this.ShowFormButton == FormButtons.Approver) { @@ -170,6 +311,7 @@   } + @@ -177,11 +319,12 @@ Uploading ...
    -
    + Thank you for the project idea! + @code{ public enum FormButtons { @@ -197,6 +340,8 @@ } } + private bool IsNotRobotChecked { get; set; } = false; + [Parameter] public bool ShowFileAttachments { get; set; } = false; @@ -237,7 +382,9 @@ private string tableState = "block"; - private string waitBar => tableState.Equals("none") ? "visible" : "hidden"; + private string waitBar => tableState.Equals("none") ? "visible" : "hidden" ; + + protected override void OnInitialized() { @@ -251,6 +398,17 @@ private async Task HandleValidSubmit() { + Console.WriteLine("SUBMITTING A VALID PROJECT"); + var reCaptchaResponse = await JSRuntime.InvokeAsync("grecaptcha.getResponse"); + + if (string.IsNullOrEmpty(reCaptchaResponse)) + { + await JSRuntime.InvokeVoidAsync("alert", "Please complete the reCAPTCHA verification."); + return; + } + + IsNotRobotChecked = true; + string link = ""; if (!idea.Url.StartsWith("http://") && !idea.Url.StartsWith("https://") && idea.Url.Length != 0) { @@ -270,43 +428,112 @@ idea.VideoLink = link; } - this.tableState = "none"; + idea.TeamMember = initialValue; - await Task.Run( async () => + + idea.LinkName = initialValueLinkName; + + + + idea.Url = initialValueUrl; + + idea.SetMembers(teamMembers); + idea.SetLinks(links); + idea.SetLinksURL(linkUrl); + + + tableState = "none"; + StateHasChanged(); + + await Task.Delay(2000); + + var uploaded = await ProjectManager.AddAsync(idea); + + if (uploaded) { - var uploaded = await ProjectManager.AddAsync(idea); + // Clear the form and reset state + idea.IsDirty = false; + teamMembers.Clear(); + loadedFiles.Clear(); + links.Clear(); + linkUrl.Clear(); + idea = new ProjectInformation(); + } + // await Task.Delay(2000); - if (uploaded) - { - idea.IsDirty = false; - loadedFiles.Clear(); - idea = new ProjectInformation(); - } + tableState = "block"; + @* await JSRuntime.InvokeVoidAsync("grecaptcha.reset"); *@ + StateHasChanged(); + + Email("rwilson@umgc.dev", "New Project Idea submitted", "A new Project Idea has been submitted. Please visit the CaPPMS website to approve or reject the idea."); - await Task.Delay(2000); - }); - this.tableState = "block"; } - private void LoadFiles(InputFileChangeEventArgs inputFile) + private async Task Email(string email, string body, string html) { - this.loadedFiles.Clear(); - foreach (var file in inputFile.GetMultipleFiles().Take(ProjectManager.MaxNumberOfFiles)) + Console.WriteLine("REACHED SENDING EMAIL"); + try { - if (file.Size > ProjectManager.MaxMBSizePerFile) - { - continue; - } + await MailService.SendEmailAsync(email, body, html); + } + catch (Exception ex) + { + Console.WriteLine($"Error sending email: {ex.Message}"); + } + } + + + + private int uploadProgress = 0; + + private async void LoadFiles(InputFileChangeEventArgs inputFile) + { + var newFiles = inputFile.GetMultipleFiles() + .Take(ProjectManager.MaxNumberOfFiles) + .Where(file => file.Size <= ProjectManager.MaxMBSizePerFile) + .Select(file => new ProjectFile(file)); + + this.loadedFiles = this.loadedFiles.Concat(newFiles).ToList(); + + @* this.loadedFiles = this.loadedFiles.Where(file => !ShouldRemoveFile(file)).ToList(); *@ - var projectFile = new ProjectFile(file); - this.loadedFiles.Add(projectFile); + var totalFiles = loadedFiles.Count; + var iterations = 10; + + for (var i = 0; i < iterations; i++) + { + var progress = (int)(((float)i / (iterations - 1)) * 100); + uploadProgress = progress; + StateHasChanged(); + + await Task.Delay(500); } + + + idea.SetAttachments(loadedFiles); + + + + uploadProgress = 0; + await Task.Yield(); + StateHasChanged(); + + + + } + + // Add this method to remove files based on their filenames + private void RemoveFileByName(string fileName) + { + this.loadedFiles = this.loadedFiles.Where(file => file.Name != fileName).ToList(); + // Update attachments after removing the file idea.SetAttachments(loadedFiles); } + private void SponsorCheckBox_Changed(EventArgs e) { this.idea.IsSponsor = !this.idea.IsSponsor; @@ -394,4 +621,132 @@ { NavigationManager.NavigateTo($"/download/{file.Location}", true); } -} \ No newline at end of file + + + // Define a list to store team members + List teamMembers = new List(); + + // Function to add a new team member input field + private void AddTeamMember() + { + // Add an empty string to the list + teamMembers.Add(""); + + } + String initialValue = ""; + private void setInitialTeamMemberValue(string value) + { + initialValue = value; + } + + String initialValueLinkName = ""; + private void setInitialLinkNameValue(string value) + { + initialValueLinkName = value; + } + + String initialValueUrl = ""; + private void setInitialUrlValue(string value) + { + initialValueUrl = value; + } + private void UpdateIdeaTeamMembers(int index, string value) + { + teamMembers[index] = value; // Update the teamMembers array + + // Update idea's TeamMembers property + // idea.TeamMember = initialValue + " , " + string.Join(", ", teamMembers.Where(tm => !string.IsNullOrWhiteSpace(tm))); + } + + // Function to remove the last added team member input field +private void RemoveLastTeamMember() +{ + // Check if there are any team members in the list + if (teamMembers.Count > 0) + { + // Remove the last added team member (which is at the end of the list) + teamMembers.RemoveAt(teamMembers.Count - 1); + + // Update idea's TeamMembers property + // idea.TeamMember = string.Join(", ", teamMembers.Where(tm => !string.IsNullOrWhiteSpace(tm))); + } +} + + + List links = new List(); + // Function to add a new link input field + private void AddLink() + { + // Add an empty string to the list + links.Add(""); + } + + private void UpdateIdeaLinkName(int index, string value) + { + links[index] = value; // Update the teamMembers array + + // Update idea's TeamMembers property + @* idea.LinkName = string.Join(", ", links.Where(tm => !string.IsNullOrWhiteSpace(tm))); *@ + } + + List linkUrl = new List(); + private void AddUrlLink() + { + linkUrl.Add(""); + } + + public void AddLinkandUrl() +{ + // Add a new link and URL entry + links.Add(""); // Add an empty string for the new link name + linkUrl.Add(""); // Add an empty string for the new link URL +} + +public void RemoveLinkandURL() +{ + RemoveLastLink(); + RemoveLastUrlLink(); +} + + + + private void UpdateIdeaUrl(int index, string value) + { + linkUrl[index] = value; // Update the teamMembers array + + // Update idea's TeamMembers property + @* idea.Url = string.Join(", ", linkUrl.Where(tm => !string.IsNullOrWhiteSpace(tm))); *@ + } + + + // Function to remove the last added link input field +private void RemoveLastLink() +{ + // Check if there are any links in the list + if (links.Count > 0) + { + // Remove the last added link (which is at the end of the list) + links.RemoveAt(links.Count - 1); + + @* idea.LinkNames = string.Join(", ", links.Where(tm => !string.IsNullOrWhiteSpace(tm))); *@ + } +} + +// Function to remove the last added URL link input field +private void RemoveLastUrlLink() +{ + // Check if there are any URL links in the list + if (linkUrl.Count > 0) + { + // Remove the last added URL link (which is at the end of the list) + linkUrl.RemoveAt(linkUrl.Count - 1); + + @* idea.Url = string.Join(", ", linkUrl.Where(tm => !string.IsNullOrWhiteSpace(tm))); *@ + } +} + + + +} + + diff --git a/CaPPMS/Shared/LoginDisplay.razor b/CaPPMS/Shared/LoginDisplay.razor index 0ca5f80..8db75be 100644 --- a/CaPPMS/Shared/LoginDisplay.razor +++ b/CaPPMS/Shared/LoginDisplay.razor @@ -1,13 +1,13 @@  -   - @context.User.Identity.Name! - Log out - + @context.User.Identity.Name + -   - Log in - + \ No newline at end of file diff --git a/CaPPMS/Shared/ProjectTable.razor b/CaPPMS/Shared/ManageProjectTable.razor similarity index 73% rename from CaPPMS/Shared/ProjectTable.razor rename to CaPPMS/Shared/ManageProjectTable.razor index 866a2df..b88bb96 100644 --- a/CaPPMS/Shared/ProjectTable.razor +++ b/CaPPMS/Shared/ManageProjectTable.razor @@ -1,11 +1,19 @@ -@using CaPPMS.Data +@using CaPPMS.Data @using CaPPMS.Model @using CaPPMS.Model.Table @namespace CaPPMS.Shared @inherits CaPPMS.Model.Table.Table @implements IIdea -

    Project List

    +@*
    +

    Projects

    +

    + The Master of Information Systems with a Specialization in Software + Engineering program at the University of Maryland Global Campus ends + with a capstone course. This website allows users to view past projects. + The information available on this website is public domain. +

    *@ + @if(this.HeaderRow.Count == 0) { @@ -22,34 +30,66 @@ else placeholder="Filter the data here" @bind="Filter" @bind:event="oninput" /> - -
    - +

    Sponsor Information

    + + + - - - - - + - - -
    + +
    - + + +
    - +
    + +
    - + + +
    } +@*
    + +
    + @if(loadedFiles.Any()) + { +
    +
    + Attached Files: +
      + @foreach (var file in loadedFiles) + { +
    • @file.Name + +
    • + } +
    +
    +
    + } + +
    +
    +
    +
    + + + +
    + + + @*
    *@ +
    - @foreach (var header in this.HeaderRow) { } - @foreach (var row in this.GetRows()) { - - @foreach (var cell in row) + *@ + + @foreach (var cell in row.Select((value, index) => new { value, index })) { - @((MarkupString)cell.ToString()) + if (cell.index == 0) + { + var ProjectID = (row.DataBoundItem as ProjectInformation).ProjectID; + + + } + else + { + + } } + + @* @foreach (var cell in row) + { + + @((MarkupString)cell.ToString()) + } *@ @if (!projectListCollapseMap[row.DataBoundItem as ProjectInformation]) @@ -71,7 +111,6 @@ else ProjectInformation="row.DataBoundItem as ProjectInformation"> } - } @@ -87,6 +126,7 @@ else
    @header.Value
    + @* - + @* @((MarkupString)cell.value.ToString()) *@ + @* @((MarkupString)cell.value.ToString()) *@ + @* @((MarkupString)cell.value.ToString()) *@ + @((MarkupString)cell.value.ToString()) + + @((MarkupString)cell.value.ToString()) + + @((MarkupString)cell.ToString()) +
    + @if (idea != null) {
    @@ -101,7 +141,6 @@ else } @code{ - [CascadingParameter] private Task authenticationState { get; set; } diff --git a/CaPPMS/Shared/NavMenu.razor.css b/CaPPMS/Shared/NavMenu.razor.css index acc5f9f..e6ae04a 100644 --- a/CaPPMS/Shared/NavMenu.razor.css +++ b/CaPPMS/Shared/NavMenu.razor.css @@ -1,5 +1,6 @@ .navbar-toggler { background-color: rgba(255, 255, 255, 0.1); + z-index: 1100; } .top-row { diff --git a/CaPPMS/Shared/ProjectEntryAuthorizedDropdown.razor b/CaPPMS/Shared/ProjectEntryAuthorizedDropdown.razor index 4234388..f3f53d3 100644 --- a/CaPPMS/Shared/ProjectEntryAuthorizedDropdown.razor +++ b/CaPPMS/Shared/ProjectEntryAuthorizedDropdown.razor @@ -72,13 +72,15 @@
    Project Title: Project Description:Team Member:Link Name
    @@ -332,6 +334,8 @@ _infoPlaceholder.VideoLink = projectInformation.VideoLink; _infoPlaceholder.ProjectTitle = projectInformation.ProjectTitle; _infoPlaceholder.ProjectDescription = projectInformation.ProjectDescription; + _infoPlaceholder.TeamMember = projectInformation.TeamMember; + _infoPlaceholder.LinkName = projectInformation.LinkName; _infoPlaceholder.Url = projectInformation.Url; _infoPlaceholder.Github = projectInformation.Github; _infoPlaceholder.FirstName = projectInformation.FirstName; @@ -409,6 +413,8 @@ projectInformation.VideoLink = _infoPlaceholder.VideoLink; projectInformation.ProjectTitle = _infoPlaceholder.ProjectTitle; projectInformation.ProjectDescription = _infoPlaceholder.ProjectDescription; + projectInformation.TeamMember = _infoPlaceholder.TeamMember; + projectInformation.LinkName = _infoPlaceholder.LinkName; projectInformation.Url = _infoPlaceholder.Url; projectInformation.Github = _infoPlaceholder.Github; projectInformation.FirstName = _infoPlaceholder.FirstName; diff --git a/CaPPMS/Shared/ProjectsTable.razor b/CaPPMS/Shared/ProjectsTable.razor new file mode 100644 index 0000000..65dd2a4 --- /dev/null +++ b/CaPPMS/Shared/ProjectsTable.razor @@ -0,0 +1,328 @@ +@using CaPPMS.Data +@using CaPPMS.Model +@using CaPPMS.Model.Table +@namespace CaPPMS.Shared +@inherits CaPPMS.Model.Table.Table +@implements IIdea + +@*
    +

    Projects

    +

    + The Master of Information Systems with a Specialization in Software + Engineering program at the University of Maryland Global Campus ends + with a capstone course. This website allows users to view past projects. + The information available on this website is public domain. +

    *@ + + +@if(this.HeaderRow.Count == 0) +{ +
    Loading ...
    +} +else +{ +
    +
    + + + @* *@ +
    + + + @* + @foreach (var header in this.HeaderRow) + { + @if (@header.Value == "Edit Project"){ + + } + else{ + + } + } + *@ + + @foreach (var header in this.HeaderRow) + { + + } + + + + + + @foreach (var row in this.GetRows()) + { + + + + + @foreach (var cell in row.Select((value, index) => new { value, index })) + { + bool isLastCell = cell.index == row.Count() - 1; + + if (cell.index == 0) + { + var ProjectID = (row.DataBoundItem as ProjectInformation).ProjectID; + + + } + else + { + var ProjectID = (row.DataBoundItem as ProjectInformation).ProjectID; + + } + else + { + // Render the + } + } *@ + } + + + + } + } + + @if (!projectListCollapseMap[row.DataBoundItem as ProjectInformation]) + { + + + + } + } + + + + + + +
    + + @header.Value + + @header.Value TEST +
    + @header.Value + @if (header.ColumnId == 3) // Assuming ColumnId indicates the column number + { + } +
    + @((MarkupString)cell.value.ToString()) + + @((MarkupString)cell.value.ToString()) + @* @if (isLastCell) + { +
    + s + + s +
    + } *@ + + + @if (isLastCell) + { +
    + sp + + sp +
    + } +
    + + @if (isLastCell) + { +
    + s + + s +
    + @* @foreach (var header in this.HeaderRow) + { + if (header.ColumnId == 3) // Check if the column is the third one + { + // Render the
    element with a style that hides it + + @header.Value + element normally for all other columns + + @header.Value +
    + @if (isAuthenticated) + { + + + + + } + else + { + + + } +
    + +
    +
    + + @if (idea != null) + { +
    + + + +
    + } +
    +
    +
    +} + +@code{ + [CascadingParameter] + private Task authenticationState { get; set; } + + [Parameter] + public string CssClass + { + get + { + return string.Join(" ", this.CssClass); + } + set + { + this.TableCssClasses.Clear(); + this.TableCssClasses.AddRange(value.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); + } + } + + private List TableCssClasses = new List(); + + private ProjectInformation idea { get; set; } + + + private bool isAuthenticated; + + private Dictionary projectListCollapseMap = new Dictionary(); + + protected override async Task OnInitializedAsync() + { + + var authState = await authenticationState; + + this.isAuthenticated = authState.User?.Identity.IsAuthenticated ?? false; + InitializeCollapseMap(); + + this.DataSourceChanged += (o, e) => + { + if (idea != null && !this.DataSource.Contains(idea)) + { + idea = null; + } + + this.ShouldProcessStateChange(); + InitializeCollapseMap(); + }; + + this.FilterChanged += (o, e) => + { + this.SetPageNumber(1); + this.ShouldProcessStateChange(); + InitializeCollapseMap(); + }; + + ProjectManagerService.ProjectIdeasChanged += (o, e) => + { + var ideas = o as ICollection; + + this.DataSource = ideas; + }; + + await base.OnInitializedAsync(); + } + + public void CloseIdea(Idea idea) + { + this.idea = null; + ShouldProcessStateChange(); + } + + private void PageRight(MouseEventArgs e) + { + this.SetPageNumber(this.CurrentPage + 1); + } + + private void ShowView(ProjectInformation idea) + { + this.idea = idea; + } + + private void ShouldProcessStateChange() + { + this.InvokeAsync(() => + { + this.StateHasChanged(); + }); + } + + private void InitializeCollapseMap() + { + Dictionary oldCollapseMap = new Dictionary(); + foreach (ProjectInformation info in projectListCollapseMap.Keys) + { + oldCollapseMap.Add(info.ProjectID, projectListCollapseMap[info]); + } + + projectListCollapseMap.Clear(); + foreach (Row row in this.GetRows()) + { + if (!projectListCollapseMap.ContainsKey(row.DataBoundItem as ProjectInformation)) + { + ProjectInformation newInfo = row.DataBoundItem as ProjectInformation; + projectListCollapseMap.Add(newInfo, true); + if (oldCollapseMap.ContainsKey(newInfo.ProjectID)) + { + projectListCollapseMap[newInfo] = oldCollapseMap[newInfo.ProjectID]; + } + } + } + } + + private void SortByColumn(int column) + { + Console.Error.WriteLine(column + " clicked"); + if (SortColumnIndex == column) + { + IsColumnSortAscending = !IsColumnSortAscending; + } else + { + IsColumnSortAscending = true; + SortColumnIndex = column; + } + InitializeCollapseMap(); + } +} diff --git a/CaPPMS/Shared/SubmitIdea.razor b/CaPPMS/Shared/SubmitIdea.razor index b42064e..9ceb32c 100644 --- a/CaPPMS/Shared/SubmitIdea.razor +++ b/CaPPMS/Shared/SubmitIdea.razor @@ -1,16 +1,22 @@ @using CaPPMS.Data @inject ProjectManagerService ProjectManager -
    +
    -
    - Idea -
    -
    -
    - Software Development Project Proposal -
    -
    +
    +
    +

    Submit Project

    +

    + Welcome to the project submission portal for the Master of Information Systems with a Specialization in + Software Engineering program at the University of Maryland Global Campus. + Here, individuals and organizations propose project ideas that will allow students to demonstrate their software engineering expertise within information systems. These submissions foster creativity, problem-solving, and innovation, + encouraging alignment with industry demands in areas like web development, cybersecurity, and emerging + technologies. Collaboration with mentors and professionals is encouraged to push boundaries and make + impactful contributions to software engineering. + These projects shape our learning community and prepare students for successful careers. + Best wishes for your submissions; we're excited to see your innovative solutions. +

    +
    @@ -20,5 +26,5 @@ @code { - + } \ No newline at end of file diff --git a/CaPPMS/Shared/TeamSelection.razor b/CaPPMS/Shared/TeamSelection.razor index 4dff523..3e5cbea 100644 --- a/CaPPMS/Shared/TeamSelection.razor +++ b/CaPPMS/Shared/TeamSelection.razor @@ -7,7 +7,7 @@ -
    +
    +
    + + +@code { + private bool collapseNavMenu = true; + + private string NavMenuCssClass => collapseNavMenu ? "collapse navbar-collapse" : "navbar-collapse"; + + private void ToggleNavbar() + { + collapseNavMenu = !collapseNavMenu; + } +} diff --git a/CaPPMS/Shared/UMGCHeader.razor.css b/CaPPMS/Shared/UMGCHeader.razor.css index 3718a86..1c2ee14 100644 --- a/CaPPMS/Shared/UMGCHeader.razor.css +++ b/CaPPMS/Shared/UMGCHeader.razor.css @@ -1,7 +1,22 @@ -.navbar { - margin-bottom: 0; - border-radius: 0; - background-color: #960513; - height: 60px; - padding-top: 10px; -} \ No newline at end of file +/* Inside your custom CSS file */ + +/* Navbar styles */ + +/* For desktop view (viewport width >= 992px) */ +@media (min-width: 992px) { + .navbar-nav .nav-link { + color: white !important; + } + + .navbar-nav .nav-link.login-link { + color: white !important; + } +} + +/* For mobile view (viewport width <= 991px) */ +@media (max-width: 991px) { + .navbar-nav .nav-link { + color: white !important; + } + +} diff --git a/CaPPMS/Startup.cs b/CaPPMS/Startup.cs index bfa430e..a525cef 100644 --- a/CaPPMS/Startup.cs +++ b/CaPPMS/Startup.cs @@ -48,6 +48,8 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(Configuration.GetSection("MailSettings").Get()); + services.AddScoped(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/CaPPMS/appsettings.json b/CaPPMS/appsettings.json index 7282fb9..d1063c1 100644 --- a/CaPPMS/appsettings.json +++ b/CaPPMS/appsettings.json @@ -22,6 +22,13 @@ For more info see https://aka.ms/dotnet-template-ms-identity-platform "BaseUrl": "https://graph.microsoft.com/v1.0", "Scopes": "user.read Directory.AccessAsUser.All" }, + "MailSettings": { + "Username": "cappmsideas@gmail.com", + "Password": "jaej izww dzdf rdxk", + "Port": 587, + "FromEmail": "cappmsideas@gmail.com", + "Host": "smtp.gmail.com" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/CaPPMS/wwwroot/css/site.css b/CaPPMS/wwwroot/css/site.css index 53c9736..d0fed47 100644 --- a/CaPPMS/wwwroot/css/site.css +++ b/CaPPMS/wwwroot/css/site.css @@ -10,9 +10,9 @@ a, color: #0366d6; } -.btn-primary { +button.btn-primary { color: #fff; - background-color: #da1a32; + background-color: #a30606; border-color: #a30606; } @@ -57,6 +57,14 @@ a, top: 0.5rem; } +table { + border-collapse: collapse; +} + +table.borderless td { + border: none; +} + tr th { background-color : black; color: yellow; @@ -64,7 +72,23 @@ tr th { tr { padding: 2px; - text-align:center; + text-align:left; +} + +#submitAttachments { + text-align: center; + height:10em; + background-color:#EEEEEE; + border-radius: 25px; + padding: 2em 3em 3em 3em; + align-items: center; +} + +#attachment { + /* opacity: 0; */ + width: 400px; + background-color: #a30606; + border-color: #a30606; } .odd { @@ -94,8 +118,6 @@ navLink-active { margin-right: auto; margin-bottom: 5px; padding: 3px; - border-radius: 5px; - border: 2px solid darkred; overflow: auto; } @@ -162,4 +184,32 @@ navLink-active { .center { text-align: center; padding:10px 0; -} \ No newline at end of file +} + + .progress { + width: 100%; + height: 20px; + background-color: #f0f0f0; + border-radius: 10px; + margin-top: 10px; + position: relative; + overflow: hidden; + } + .progress-bar { + width: 0%; + height: 100%; + background-color: #007bff; + border-radius: 10px; + animation: progressBarAnimation 5s linear forwards; /* Adjust animation duration as needed */ + } + @keyframes progressBarAnimation { + from { + width: 0%; + } + to { + width: 100%; + } + } + .complete { + background-color: #28a745 !important; + } diff --git a/CaPPMSTests/CaPPMSTests.csproj b/CaPPMSTests/CaPPMSTests.csproj index c2e4b69..2f0b70e 100644 --- a/CaPPMSTests/CaPPMSTests.csproj +++ b/CaPPMSTests/CaPPMSTests.csproj @@ -1,7 +1,7 @@  - .NETCoreApp,Version=v5.0 + net8.0 false diff --git a/web/index.html b/web/index.html index 4c11e48..45c776f 100644 --- a/web/index.html +++ b/web/index.html @@ -25,6 +25,7 @@ DevSecOpsPlayground project. +