From 754cc5bfff7164576e370e71a20e1f53065f55b3 Mon Sep 17 00:00:00 2001 From: Dimitar Byalkov Date: Mon, 10 Apr 2023 22:09:26 +0200 Subject: [PATCH] Basic announcements added --- .../HouseData/Managers/AnnouncementManager.cs | 30 ++- .../HouseData/Models/Announcement.cs | 4 + .../Repositories/AnnouncementRepository.cs | 80 ++++++-- .../Repositories/SqlConnectionHelper.cs | 28 +++ .../HouseData/Repositories/UserRepository.cs | 29 +-- .../WebApp/Pages/Announcement.cshtml | 24 ++- .../WebApp/Pages/Announcement.cshtml.cs | 5 +- .../WebApp/Pages/Announcements.cshtml | 19 +- .../WebApp/Pages/Announcements.cshtml.cs | 9 +- .../WinForms/AnnouncementForm.Designer.cs | 165 +++++++++++++++++ .../WinForms/AnnouncementForm.cs | 69 +++++++ .../WinForms/AnnouncementForm.resx | 60 ++++++ .../WinForms/Dashboard.Designer.cs | 172 ++++++++++++++++-- StudentHouseDashboard/WinForms/Dashboard.cs | 50 +++++ .../WinForms/WinForms.csproj | 6 + docs/dbdiagram.png | Bin 0 -> 15024 bytes 16 files changed, 670 insertions(+), 80 deletions(-) create mode 100644 StudentHouseDashboard/HouseData/Repositories/SqlConnectionHelper.cs create mode 100644 StudentHouseDashboard/WinForms/AnnouncementForm.Designer.cs create mode 100644 StudentHouseDashboard/WinForms/AnnouncementForm.cs create mode 100644 StudentHouseDashboard/WinForms/AnnouncementForm.resx create mode 100644 docs/dbdiagram.png diff --git a/StudentHouseDashboard/HouseData/Managers/AnnouncementManager.cs b/StudentHouseDashboard/HouseData/Managers/AnnouncementManager.cs index 197b6e4..3acab31 100644 --- a/StudentHouseDashboard/HouseData/Managers/AnnouncementManager.cs +++ b/StudentHouseDashboard/HouseData/Managers/AnnouncementManager.cs @@ -1,5 +1,8 @@ -using System; +using StudentHouseDashboard.Models; +using StudentHouseDashboard.Repositories; +using System; using System.Collections.Generic; +using System.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,5 +11,30 @@ namespace StudentHouseDashboard.Managers { public class AnnouncementManager { + private AnnouncementRepository announcementRepository; + public AnnouncementManager() + { + announcementRepository = new AnnouncementRepository(); + } + public List GetAllAnnouncements() + { + return announcementRepository.GetAllAnnouncements(); + } + public List GetAnnouncementsByPage(int? p, int? c) + { + return announcementRepository.GetAnnouncementsByPage(p, c); + } + public bool CreateAnnouncement(string title, string description, User author, DateTime publishDate, bool isImportant, bool isSticky) + { + return announcementRepository.CreateAnnouncement(title, description, author, publishDate, isImportant, isSticky); + } + public bool UpdateAnnouncement(int id, string title, string description, User author, DateTime publishDate, bool isImportant, bool isSticky) + { + return announcementRepository.UpdateAnnouncement(id, title, description, author, publishDate, isImportant, isSticky); + } + public bool DeleteAnnouncement(int id) + { + return announcementRepository.DeleteAnnouncement(id); + } } } diff --git a/StudentHouseDashboard/HouseData/Models/Announcement.cs b/StudentHouseDashboard/HouseData/Models/Announcement.cs index c020318..2ef3e40 100644 --- a/StudentHouseDashboard/HouseData/Models/Announcement.cs +++ b/StudentHouseDashboard/HouseData/Models/Announcement.cs @@ -37,5 +37,9 @@ namespace StudentHouseDashboard.Models { throw new NotImplementedException(); } + public override string ToString() + { + return $"{Title} ({PublishDate.ToString("g")} - {Author.Name})"; + } } } \ No newline at end of file diff --git a/StudentHouseDashboard/HouseData/Repositories/AnnouncementRepository.cs b/StudentHouseDashboard/HouseData/Repositories/AnnouncementRepository.cs index 9f20f63..45ea60e 100644 --- a/StudentHouseDashboard/HouseData/Repositories/AnnouncementRepository.cs +++ b/StudentHouseDashboard/HouseData/Repositories/AnnouncementRepository.cs @@ -11,27 +11,12 @@ namespace StudentHouseDashboard.Repositories { public class AnnouncementRepository { - private string connectionString = "Server=mssqlstud.fhict.local;Database=dbi509645;User Id=dbi509645;Password=sNPNBm*BX!6z8RM;"; public AnnouncementRepository() { } - private SqlConnection CreateConnection() - { - SqlConnection connection = new SqlConnection(connectionString); - try - { - connection.Open(); - } - catch (Exception) - { - Console.WriteLine("Database connection error. Are you connected to the VDI VPN?"); - } - - return connection; - } public List GetAllAnnouncements() { List announcements = new List(); UserManager userManager = new UserManager(); - using (SqlConnection conn = CreateConnection()) + using (SqlConnection conn = SqlConnectionHelper.CreateConnection()) { string sql = "SELECT * FROM Announcements;"; SqlCommand cmd = new SqlCommand(sql, conn); @@ -62,7 +47,7 @@ namespace StudentHouseDashboard.Repositories { p = 0; } - using (SqlConnection conn = CreateConnection()) + using (SqlConnection conn = SqlConnectionHelper.CreateConnection()) { string sql = "SELECT * FROM Announcements ORDER BY ID OFFSET @start ROWS FETCH NEXT @count ROWS ONLY;"; SqlCommand sqlCommand = new SqlCommand(sql, conn); @@ -81,5 +66,66 @@ namespace StudentHouseDashboard.Repositories } return announcements; } + public bool CreateAnnouncement(string title, string description, User author, DateTime publishDate, bool isImportant, bool isSticky) + { + using (SqlConnection conn = SqlConnectionHelper.CreateConnection()) + { + string sql = "INSERT INTO Announcements (Author, Description, Title, PublishDate, IsImportant, IsSticky) VALUES (@author, @desc, @title, @date, @important, @sticky);"; + SqlCommand cmd = new SqlCommand(sql, conn); + cmd.Parameters.AddWithValue("@author", author.ID); + cmd.Parameters.AddWithValue("@desc", description); + cmd.Parameters.AddWithValue("@title", title); + cmd.Parameters.AddWithValue("@date", publishDate); + cmd.Parameters.AddWithValue("@important", isImportant); + cmd.Parameters.AddWithValue("@sticky", isSticky); + int writer = cmd.ExecuteNonQuery(); + + if (writer == 1) + { + return true; + } + else return false; + } + } + public bool UpdateAnnouncement(int id, string title, string description, User author, DateTime publishDate, bool isImportant, bool isSticky) + { + using (SqlConnection conn = SqlConnectionHelper.CreateConnection()) + { + string sql = "UPDATE Announcements " + + "SET Author = @author, Description = @desc, Title = @title, PublishDate = @date, IsImportant = @important, IsSticky = @sticky " + + "WHERE Id = @id;"; + SqlCommand cmd = new SqlCommand(sql, conn); + cmd.Parameters.AddWithValue("@author", author.ID); + cmd.Parameters.AddWithValue("@desc", description); + cmd.Parameters.AddWithValue("@title", title); + cmd.Parameters.AddWithValue("@date", publishDate); + cmd.Parameters.AddWithValue("@important", isImportant); + cmd.Parameters.AddWithValue("@sticky", isSticky); + cmd.Parameters.AddWithValue("@id", id); + int writer = cmd.ExecuteNonQuery(); + + if (writer == 1) + { + return true; + } + else return false; + } + } + public bool DeleteAnnouncement(int id) + { + using (SqlConnection conn = SqlConnectionHelper.CreateConnection()) + { + string sql = "DELETE FROM Announcements WHERE Id = @id;"; + SqlCommand cmd = new SqlCommand(sql, conn); + cmd.Parameters.AddWithValue("@id", id); + int writer = cmd.ExecuteNonQuery(); + + if (writer == 1) + { + return true; + } + else return false; + } + } } } diff --git a/StudentHouseDashboard/HouseData/Repositories/SqlConnectionHelper.cs b/StudentHouseDashboard/HouseData/Repositories/SqlConnectionHelper.cs new file mode 100644 index 0000000..3047e3e --- /dev/null +++ b/StudentHouseDashboard/HouseData/Repositories/SqlConnectionHelper.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StudentHouseDashboard.Repositories +{ + public static class SqlConnectionHelper + { + private static string connectionString = "Server=mssqlstud.fhict.local;Database=dbi509645;User Id=dbi509645;Password=sNPNBm*BX!6z8RM;"; + public static SqlConnection CreateConnection() + { + SqlConnection connection = new SqlConnection(connectionString); + try + { + connection.Open(); + } + catch (Exception) + { + Console.WriteLine("Database connection error. Are you connected to the VDI VPN?"); + } + + return connection; + } + } +} diff --git a/StudentHouseDashboard/HouseData/Repositories/UserRepository.cs b/StudentHouseDashboard/HouseData/Repositories/UserRepository.cs index 8b87abe..76b9c3c 100644 --- a/StudentHouseDashboard/HouseData/Repositories/UserRepository.cs +++ b/StudentHouseDashboard/HouseData/Repositories/UserRepository.cs @@ -12,28 +12,12 @@ namespace StudentHouseDashboard.Repositories { public class UserRepository { - private string connectionString = "Server=mssqlstud.fhict.local;Database=dbi509645;User Id=dbi509645;Password=sNPNBm*BX!6z8RM;"; - public UserRepository() { } - private SqlConnection CreateConnection() - { - SqlConnection connection = new SqlConnection(connectionString); - try - { - connection.Open(); - } - catch (Exception) - { - Console.WriteLine("Database connection error. Are you connected to the VDI VPN?"); - } - - return connection; - } public List GetAllUsers() { var users = new List(); - using (SqlConnection conn = CreateConnection()) + using (SqlConnection conn = SqlConnectionHelper.CreateConnection()) { string sql = "SELECT * FROM Users;"; SqlCommand cmd = new SqlCommand(sql, conn); @@ -54,7 +38,7 @@ namespace StudentHouseDashboard.Repositories } public User GetUserById(int id) { - using (SqlConnection conn = CreateConnection()) + using (SqlConnection conn = SqlConnectionHelper.CreateConnection()) { string sql = "SELECT * FROM Users WHERE ID = @id;"; SqlCommand cmd = new SqlCommand(sql, conn); @@ -81,7 +65,7 @@ namespace StudentHouseDashboard.Repositories { p = 0; } - using (SqlConnection conn = CreateConnection()) + using (SqlConnection conn = SqlConnectionHelper.CreateConnection()) { string sql = "SELECT * FROM Users ORDER BY ID OFFSET @start ROWS FETCH NEXT @count ROWS ONLY;"; SqlCommand sqlCommand = new SqlCommand(sql, conn); @@ -99,7 +83,7 @@ namespace StudentHouseDashboard.Repositories } public bool CreateUser(string name, string password, UserRole role) { - using (SqlConnection conn = CreateConnection()) + using (SqlConnection conn = SqlConnectionHelper.CreateConnection()) { string sql = "INSERT INTO Users (Name, Password, Role) VALUES (@name, @pass, @role);"; SqlCommand cmd = new SqlCommand(sql, conn); @@ -117,7 +101,7 @@ namespace StudentHouseDashboard.Repositories } public bool UpdateUser(int id, string name, string password, UserRole role) { - using (SqlConnection conn = CreateConnection()) + using (SqlConnection conn = SqlConnectionHelper.CreateConnection()) { string sql = "UPDATE Users " + "SET Name = @name, Password = @pass, Role = @role " + @@ -138,7 +122,7 @@ namespace StudentHouseDashboard.Repositories } public bool DisableUser(int id) { - using (SqlConnection conn = CreateConnection()) + using (SqlConnection conn = SqlConnectionHelper.CreateConnection()) { string sql = "UPDATE Users " + "SET Name = 'Deleted User @id', Password = '0'" + @@ -154,6 +138,5 @@ namespace StudentHouseDashboard.Repositories else return false; } } - } } diff --git a/StudentHouseDashboard/WebApp/Pages/Announcement.cshtml b/StudentHouseDashboard/WebApp/Pages/Announcement.cshtml index bfecd60..f77f73e 100644 --- a/StudentHouseDashboard/WebApp/Pages/Announcement.cshtml +++ b/StudentHouseDashboard/WebApp/Pages/Announcement.cshtml @@ -2,21 +2,27 @@ @using StudentHouseDashboard.Models; @model WebApp.Pages.AnnouncementModel @{ - User user = (User)ViewData["user"]; - ViewData["Title"] = $"User {user.Name}"; + Announcement announcement = (Announcement)ViewData["announcement"]; + ViewData["Title"] = $"{announcement.Title}"; } -

@user.Name

+

@announcement.Title

-

Name:

-

Password:

-

Role:

+

Title:

+

Author:

+

Description:

+

Date:

+

Important:

+

Pinned:

-

@user.Name

-

@user.Password

-

@user.Role.ToString()

+

@announcement.Title

+

@announcement.Author.Name

+

@announcement.Description

+

@announcement.PublishDate.ToString("g")

+

@announcement.IsImportant.ToString()

+

@announcement.IsSticky.ToString()

diff --git a/StudentHouseDashboard/WebApp/Pages/Announcement.cshtml.cs b/StudentHouseDashboard/WebApp/Pages/Announcement.cshtml.cs index c0764d1..2fab7ae 100644 --- a/StudentHouseDashboard/WebApp/Pages/Announcement.cshtml.cs +++ b/StudentHouseDashboard/WebApp/Pages/Announcement.cshtml.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using StudentHouseDashboard.Managers; +using StudentHouseDashboard.Models; namespace WebApp.Pages { @@ -10,8 +11,8 @@ namespace WebApp.Pages { public void OnGet(int id) { - UserManager userManager = new UserManager(); - ViewData.Add("user", userManager.GetUserById(id)); + AnnouncementManager announcementManager = new AnnouncementManager(); + ViewData.Add("announcement", announcementManager.GetAllAnnouncements().Where(x => x.ID == id).First()); } } } diff --git a/StudentHouseDashboard/WebApp/Pages/Announcements.cshtml b/StudentHouseDashboard/WebApp/Pages/Announcements.cshtml index 2d3c06e..3cda7e7 100644 --- a/StudentHouseDashboard/WebApp/Pages/Announcements.cshtml +++ b/StudentHouseDashboard/WebApp/Pages/Announcements.cshtml @@ -1,20 +1,25 @@ @page @using StudentHouseDashboard.Models; +@using System.Security.Claims; @model WebApp.Pages.AnnouncementsModel @{ ViewData["Title"] = "Announcements"; - List users = (List)ViewData["users"]; + List announcements = (List)ViewData["announcements"]; int currentPage = @Convert.ToInt32(ViewData["page"]); } -@foreach (User user in users) +@foreach (Announcement announcement in announcements) {
-
@user.Role.ToString()
-
@user.Name
-

@user.Password

- More details +
@announcement.Title
+
@announcement.Author.Name
+

@announcement.Description

+ More details + @if (User.FindFirst(ClaimTypes.Role).Value == "ADMIN") + { + @: Delete + }
} @@ -37,7 +42,7 @@ @:
  • @(currentPage - 1)
  • }
  • @currentPage
  • - @if (users.Count == 0) + @if (announcements.Count == 0) { @:
  • } diff --git a/StudentHouseDashboard/WebApp/Pages/Announcements.cshtml.cs b/StudentHouseDashboard/WebApp/Pages/Announcements.cshtml.cs index 7021696..f20c5ee 100644 --- a/StudentHouseDashboard/WebApp/Pages/Announcements.cshtml.cs +++ b/StudentHouseDashboard/WebApp/Pages/Announcements.cshtml.cs @@ -2,6 +2,8 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using StudentHouseDashboard.Managers; +using StudentHouseDashboard.Models; +using System.Security.Claims; namespace WebApp.Pages { @@ -9,17 +11,16 @@ namespace WebApp.Pages public class AnnouncementsModel : PageModel { public AnnouncementManager AnnouncementManager { get; set; } - public UserManager UserManager { get; set; } public void OnGet(int? p, int? c) { - UserManager = new UserManager(); + AnnouncementManager = new AnnouncementManager(); if (p == null || p < 1) { p = 1; } - ViewData.Add("users", UserManager.GetUsersByPage(p - 1, c)); + ViewData.Add("announcements", AnnouncementManager.GetAnnouncementsByPage(p - 1, c)); ViewData.Add("page", p); - ViewData.Add("allCount", UserManager.GetAllUsers().Count()); + ViewData.Add("allCount", AnnouncementManager.GetAllAnnouncements().Count()); } } } diff --git a/StudentHouseDashboard/WinForms/AnnouncementForm.Designer.cs b/StudentHouseDashboard/WinForms/AnnouncementForm.Designer.cs new file mode 100644 index 0000000..0f69d1b --- /dev/null +++ b/StudentHouseDashboard/WinForms/AnnouncementForm.Designer.cs @@ -0,0 +1,165 @@ +namespace WinForms +{ + partial class AnnouncementForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + lblTitle = new Label(); + tbTitle = new TextBox(); + lblDescription = new Label(); + tbDescription = new TextBox(); + lblPublishDate = new Label(); + btnSave = new Button(); + ckbImportant = new CheckBox(); + ckbSticky = new CheckBox(); + dtpPublishDate = new DateTimePicker(); + lblAuthor = new Label(); + SuspendLayout(); + // + // lblTitle + // + lblTitle.AutoSize = true; + lblTitle.Location = new Point(12, 9); + lblTitle.Name = "lblTitle"; + lblTitle.Size = new Size(32, 15); + lblTitle.TabIndex = 0; + lblTitle.Text = "Title:"; + // + // tbTitle + // + tbTitle.Location = new Point(94, 6); + tbTitle.Name = "tbTitle"; + tbTitle.Size = new Size(405, 23); + tbTitle.TabIndex = 1; + // + // lblDescription + // + lblDescription.AutoSize = true; + lblDescription.Location = new Point(12, 38); + lblDescription.Name = "lblDescription"; + lblDescription.Size = new Size(70, 15); + lblDescription.TabIndex = 2; + lblDescription.Text = "Description:"; + // + // tbDescription + // + tbDescription.Location = new Point(94, 35); + tbDescription.Multiline = true; + tbDescription.Name = "tbDescription"; + tbDescription.Size = new Size(405, 112); + tbDescription.TabIndex = 3; + // + // lblPublishDate + // + lblPublishDate.AutoSize = true; + lblPublishDate.Location = new Point(12, 159); + lblPublishDate.Name = "lblPublishDate"; + lblPublishDate.Size = new Size(76, 15); + lblPublishDate.TabIndex = 4; + lblPublishDate.Text = "Publish Date:"; + // + // btnSave + // + btnSave.Location = new Point(424, 180); + btnSave.Name = "btnSave"; + btnSave.Size = new Size(75, 23); + btnSave.TabIndex = 6; + btnSave.Text = "Save changes"; + btnSave.UseVisualStyleBackColor = true; + btnSave.Click += btnSave_Click; + // + // ckbImportant + // + ckbImportant.AutoSize = true; + ckbImportant.Location = new Point(300, 155); + ckbImportant.Name = "ckbImportant"; + ckbImportant.Size = new Size(79, 19); + ckbImportant.TabIndex = 7; + ckbImportant.Text = "Important"; + ckbImportant.UseVisualStyleBackColor = true; + // + // ckbSticky + // + ckbSticky.AutoSize = true; + ckbSticky.Location = new Point(385, 155); + ckbSticky.Name = "ckbSticky"; + ckbSticky.Size = new Size(78, 19); + ckbSticky.TabIndex = 8; + ckbSticky.Text = "Pin to top"; + ckbSticky.UseVisualStyleBackColor = true; + // + // dtpPublishDate + // + dtpPublishDate.Location = new Point(94, 153); + dtpPublishDate.Name = "dtpPublishDate"; + dtpPublishDate.Size = new Size(200, 23); + dtpPublishDate.TabIndex = 9; + // + // lblAuthor + // + lblAuthor.AutoSize = true; + lblAuthor.Location = new Point(12, 184); + lblAuthor.Name = "lblAuthor"; + lblAuthor.Size = new Size(70, 15); + lblAuthor.TabIndex = 10; + lblAuthor.Text = "Created by: "; + // + // AnnouncementForm + // + AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(511, 216); + Controls.Add(lblAuthor); + Controls.Add(dtpPublishDate); + Controls.Add(ckbSticky); + Controls.Add(ckbImportant); + Controls.Add(btnSave); + Controls.Add(lblPublishDate); + Controls.Add(tbDescription); + Controls.Add(lblDescription); + Controls.Add(tbTitle); + Controls.Add(lblTitle); + Name = "AnnouncementForm"; + Text = "Announcement"; + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private Label lblTitle; + private TextBox tbTitle; + private Label lblDescription; + private TextBox tbDescription; + private Label lblPublishDate; + private Button btnSave; + private CheckBox ckbImportant; + private CheckBox ckbSticky; + private DateTimePicker dtpPublishDate; + private Label lblAuthor; + } +} \ No newline at end of file diff --git a/StudentHouseDashboard/WinForms/AnnouncementForm.cs b/StudentHouseDashboard/WinForms/AnnouncementForm.cs new file mode 100644 index 0000000..3c8d4df --- /dev/null +++ b/StudentHouseDashboard/WinForms/AnnouncementForm.cs @@ -0,0 +1,69 @@ +using StudentHouseDashboard.Managers; +using StudentHouseDashboard.Models; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace WinForms +{ + public partial class AnnouncementForm : Form + { + Announcement announcement; + User currentUser; + public AnnouncementForm(Announcement? announcement, bool readOnly, User? currentUser) + { + InitializeComponent(); + this.announcement = announcement; + this.currentUser = currentUser; + if (readOnly) + { + btnSave.Enabled = false; + tbTitle.Enabled = false; + tbDescription.Enabled = false; + dtpPublishDate.Enabled = false; + ckbImportant.Enabled = false; + ckbSticky.Enabled = false; + } + if (announcement != null) + { + tbTitle.Text = announcement.Title; + lblAuthor.Text = $"Created by: {announcement.Author.Name}"; + tbDescription.Text = announcement.Description; + dtpPublishDate.Value = announcement.PublishDate; + ckbImportant.Checked = announcement.IsImportant; + ckbSticky.Checked = announcement.IsSticky; + } + if (currentUser != null) + { + lblAuthor.Text = $"Created by: {currentUser.Name}"; + } + + } + + private void btnSave_Click(object sender, EventArgs e) + { + AnnouncementManager announcementManager = new AnnouncementManager(); + if (string.IsNullOrEmpty(tbTitle.Text)) + { + MessageBox.Show("Please enter a title"); + return; + } + + if (this.announcement == null) + { + announcementManager.CreateAnnouncement(tbTitle.Text, tbDescription.Text, currentUser, dtpPublishDate.Value, ckbImportant.Checked, ckbSticky.Checked); + } + else + { + announcementManager.UpdateAnnouncement(announcement.ID, tbTitle.Text, tbDescription.Text, currentUser, dtpPublishDate.Value, ckbImportant.Checked, ckbSticky.Checked); + } + this.DialogResult = DialogResult.OK; + } + } +} diff --git a/StudentHouseDashboard/WinForms/AnnouncementForm.resx b/StudentHouseDashboard/WinForms/AnnouncementForm.resx new file mode 100644 index 0000000..f298a7b --- /dev/null +++ b/StudentHouseDashboard/WinForms/AnnouncementForm.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/StudentHouseDashboard/WinForms/Dashboard.Designer.cs b/StudentHouseDashboard/WinForms/Dashboard.Designer.cs index 5837ec5..b3d0b4e 100644 --- a/StudentHouseDashboard/WinForms/Dashboard.Designer.cs +++ b/StudentHouseDashboard/WinForms/Dashboard.Designer.cs @@ -34,80 +34,208 @@ btnDeleteUser = new Button(); btnUpdateUser = new Button(); btnViewUser = new Button(); + tabControl1 = new TabControl(); + tpUsers = new TabPage(); + panelUserFunctions = new Panel(); + tpAnnouncements = new TabPage(); + lbAnnouncements = new ListBox(); + panel1 = new Panel(); + btnNewAnnouncement = new Button(); + btnDeleteAnnouncement = new Button(); + btnViewAnnouncement = new Button(); + btnEditAnnouncement = new Button(); + tabControl1.SuspendLayout(); + tpUsers.SuspendLayout(); + panelUserFunctions.SuspendLayout(); + tpAnnouncements.SuspendLayout(); + panel1.SuspendLayout(); SuspendLayout(); // // lblUserStatus // + lblUserStatus.Anchor = AnchorStyles.Top | AnchorStyles.Right; lblUserStatus.AutoSize = true; - lblUserStatus.Location = new Point(13, 18); + lblUserStatus.Location = new Point(396, 4); lblUserStatus.Name = "lblUserStatus"; lblUserStatus.Size = new Size(80, 15); lblUserStatus.TabIndex = 0; lblUserStatus.Text = "Logged in as: "; + lblUserStatus.TextAlign = ContentAlignment.TopRight; // // lbUsers // + lbUsers.Dock = DockStyle.Fill; lbUsers.FormattingEnabled = true; lbUsers.ItemHeight = 15; - lbUsers.Location = new Point(13, 86); + lbUsers.Location = new Point(3, 3); lbUsers.Name = "lbUsers"; - lbUsers.Size = new Size(318, 154); + lbUsers.Size = new Size(717, 334); lbUsers.TabIndex = 1; // // btnCreateUser // - btnCreateUser.Location = new Point(13, 257); + btnCreateUser.Location = new Point(3, 3); btnCreateUser.Name = "btnCreateUser"; btnCreateUser.Size = new Size(75, 23); btnCreateUser.TabIndex = 2; - btnCreateUser.Text = "New User"; + btnCreateUser.Text = "New"; btnCreateUser.UseVisualStyleBackColor = true; btnCreateUser.Click += btnCreateUser_Click; // // btnDeleteUser // - btnDeleteUser.Location = new Point(94, 257); + btnDeleteUser.Location = new Point(84, 3); btnDeleteUser.Name = "btnDeleteUser"; btnDeleteUser.Size = new Size(75, 23); btnDeleteUser.TabIndex = 3; - btnDeleteUser.Text = "Delete User"; + btnDeleteUser.Text = "Delete"; btnDeleteUser.UseVisualStyleBackColor = true; btnDeleteUser.Click += btnDeleteUser_Click; // // btnUpdateUser // - btnUpdateUser.Location = new Point(175, 257); + btnUpdateUser.Location = new Point(165, 3); btnUpdateUser.Name = "btnUpdateUser"; btnUpdateUser.Size = new Size(75, 23); btnUpdateUser.TabIndex = 4; - btnUpdateUser.Text = "Edit User"; + btnUpdateUser.Text = "Edit"; btnUpdateUser.UseVisualStyleBackColor = true; btnUpdateUser.Click += btnUpdateUser_Click; // // btnViewUser // - btnViewUser.Location = new Point(256, 257); + btnViewUser.Location = new Point(246, 3); btnViewUser.Name = "btnViewUser"; btnViewUser.Size = new Size(75, 23); btnViewUser.TabIndex = 5; - btnViewUser.Text = "View User"; + btnViewUser.Text = "View"; btnViewUser.UseVisualStyleBackColor = true; btnViewUser.Click += btnViewUser_Click; // + // tabControl1 + // + tabControl1.Controls.Add(tpUsers); + tabControl1.Controls.Add(tpAnnouncements); + tabControl1.Dock = DockStyle.Fill; + tabControl1.Location = new Point(0, 0); + tabControl1.Name = "tabControl1"; + tabControl1.SelectedIndex = 0; + tabControl1.Size = new Size(731, 368); + tabControl1.TabIndex = 6; + // + // tpUsers + // + tpUsers.Controls.Add(panelUserFunctions); + tpUsers.Controls.Add(lbUsers); + tpUsers.Location = new Point(4, 24); + tpUsers.Name = "tpUsers"; + tpUsers.Padding = new Padding(3); + tpUsers.Size = new Size(723, 340); + tpUsers.TabIndex = 0; + tpUsers.Text = "Users"; + tpUsers.UseVisualStyleBackColor = true; + // + // panelUserFunctions + // + panelUserFunctions.Controls.Add(btnCreateUser); + panelUserFunctions.Controls.Add(btnDeleteUser); + panelUserFunctions.Controls.Add(btnViewUser); + panelUserFunctions.Controls.Add(btnUpdateUser); + panelUserFunctions.Dock = DockStyle.Bottom; + panelUserFunctions.Location = new Point(3, 298); + panelUserFunctions.Name = "panelUserFunctions"; + panelUserFunctions.Size = new Size(717, 39); + panelUserFunctions.TabIndex = 6; + // + // tpAnnouncements + // + tpAnnouncements.Controls.Add(panel1); + tpAnnouncements.Controls.Add(lbAnnouncements); + tpAnnouncements.Location = new Point(4, 24); + tpAnnouncements.Name = "tpAnnouncements"; + tpAnnouncements.Padding = new Padding(3); + tpAnnouncements.Size = new Size(723, 340); + tpAnnouncements.TabIndex = 1; + tpAnnouncements.Text = "Announcements"; + tpAnnouncements.UseVisualStyleBackColor = true; + // + // lbAnnouncements + // + lbAnnouncements.Dock = DockStyle.Fill; + lbAnnouncements.FormattingEnabled = true; + lbAnnouncements.ItemHeight = 15; + lbAnnouncements.Location = new Point(3, 3); + lbAnnouncements.Name = "lbAnnouncements"; + lbAnnouncements.Size = new Size(717, 334); + lbAnnouncements.TabIndex = 0; + // + // panel1 + // + panel1.Controls.Add(btnNewAnnouncement); + panel1.Controls.Add(btnDeleteAnnouncement); + panel1.Controls.Add(btnViewAnnouncement); + panel1.Controls.Add(btnEditAnnouncement); + panel1.Dock = DockStyle.Bottom; + panel1.Location = new Point(3, 298); + panel1.Name = "panel1"; + panel1.Size = new Size(717, 39); + panel1.TabIndex = 7; + // + // btnNewAnnouncement + // + btnNewAnnouncement.Location = new Point(3, 3); + btnNewAnnouncement.Name = "btnNewAnnouncement"; + btnNewAnnouncement.Size = new Size(75, 23); + btnNewAnnouncement.TabIndex = 2; + btnNewAnnouncement.Text = "New"; + btnNewAnnouncement.UseVisualStyleBackColor = true; + btnNewAnnouncement.Click += btnNewAnnouncement_Click; + // + // btnDeleteAnnouncement + // + btnDeleteAnnouncement.Location = new Point(84, 3); + btnDeleteAnnouncement.Name = "btnDeleteAnnouncement"; + btnDeleteAnnouncement.Size = new Size(75, 23); + btnDeleteAnnouncement.TabIndex = 3; + btnDeleteAnnouncement.Text = "Delete"; + btnDeleteAnnouncement.UseVisualStyleBackColor = true; + btnDeleteAnnouncement.Click += btnDeleteAnnouncement_Click; + // + // btnViewAnnouncement + // + btnViewAnnouncement.Location = new Point(246, 3); + btnViewAnnouncement.Name = "btnViewAnnouncement"; + btnViewAnnouncement.Size = new Size(75, 23); + btnViewAnnouncement.TabIndex = 5; + btnViewAnnouncement.Text = "View"; + btnViewAnnouncement.UseVisualStyleBackColor = true; + btnViewAnnouncement.Click += btnViewAnnouncement_Click; + // + // btnEditAnnouncement + // + btnEditAnnouncement.Location = new Point(165, 3); + btnEditAnnouncement.Name = "btnEditAnnouncement"; + btnEditAnnouncement.Size = new Size(75, 23); + btnEditAnnouncement.TabIndex = 4; + btnEditAnnouncement.Text = "Edit"; + btnEditAnnouncement.UseVisualStyleBackColor = true; + btnEditAnnouncement.Click += btnEditAnnouncement_Click; + // // Dashboard // AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleMode = AutoScaleMode.Font; - ClientSize = new Size(359, 302); - Controls.Add(btnViewUser); - Controls.Add(btnUpdateUser); - Controls.Add(btnDeleteUser); - Controls.Add(btnCreateUser); - Controls.Add(lbUsers); + ClientSize = new Size(731, 368); Controls.Add(lblUserStatus); + Controls.Add(tabControl1); Name = "Dashboard"; Text = "Dashboard"; FormClosed += Dashboard_FormClosed; + tabControl1.ResumeLayout(false); + tpUsers.ResumeLayout(false); + panelUserFunctions.ResumeLayout(false); + tpAnnouncements.ResumeLayout(false); + panel1.ResumeLayout(false); ResumeLayout(false); PerformLayout(); } @@ -120,5 +248,15 @@ private Button btnDeleteUser; private Button btnUpdateUser; private Button btnViewUser; + private TabControl tabControl1; + private TabPage tpUsers; + private TabPage tpAnnouncements; + private Panel panelUserFunctions; + private Panel panel1; + private Button btnNewAnnouncement; + private Button btnDeleteAnnouncement; + private Button btnViewAnnouncement; + private Button btnEditAnnouncement; + private ListBox lbAnnouncements; } } \ No newline at end of file diff --git a/StudentHouseDashboard/WinForms/Dashboard.cs b/StudentHouseDashboard/WinForms/Dashboard.cs index 6b4a9cd..b5a88ad 100644 --- a/StudentHouseDashboard/WinForms/Dashboard.cs +++ b/StudentHouseDashboard/WinForms/Dashboard.cs @@ -15,9 +15,11 @@ namespace WinForms public partial class Dashboard : Form { Login loginForm; + User user; public Dashboard(Login loginForm, User user) { this.loginForm = loginForm; + this.user = user; InitializeComponent(); lblUserStatus.Text = $"Logged in as: {user.Role} {user.Name}"; if (user.Role == UserRole.MANAGER) @@ -25,18 +27,27 @@ namespace WinForms btnCreateUser.Enabled = false; btnDeleteUser.Enabled = false; btnUpdateUser.Enabled = true; + btnNewAnnouncement.Enabled = false; + btnDeleteAnnouncement.Enabled = false; + btnEditAnnouncement.Enabled = true; } else if (user.Role == UserRole.ADMIN) { btnCreateUser.Enabled = true; btnDeleteUser.Enabled = true; btnUpdateUser.Enabled = true; + btnNewAnnouncement.Enabled = true; + btnDeleteAnnouncement.Enabled = true; + btnEditAnnouncement.Enabled = true; } else { btnCreateUser.Enabled = false; btnDeleteUser.Enabled = false; btnUpdateUser.Enabled = false; + btnNewAnnouncement.Enabled = false; + btnDeleteAnnouncement.Enabled = false; + btnEditAnnouncement.Enabled = false; } RefreshLists(); } @@ -81,11 +92,50 @@ namespace WinForms { lbUsers.Items.Add(_user); } + AnnouncementManager announcementManager = new AnnouncementManager(); + lbAnnouncements.Items.Clear(); + foreach (Announcement announcement in announcementManager.GetAllAnnouncements()) + { + lbAnnouncements.Items.Add(announcement); + } } private void Dashboard_FormClosed(object sender, FormClosedEventArgs e) { loginForm.Show(); } + + private void btnNewAnnouncement_Click(object sender, EventArgs e) + { + AnnouncementForm announcementForm = new AnnouncementForm(null, false, user); + announcementForm.ShowDialog(); + RefreshLists(); + } + + private void btnDeleteAnnouncement_Click(object sender, EventArgs e) + { + Announcement currentAnnouncement = (Announcement)lbAnnouncements.SelectedItem; + if (MessageBox.Show($"Are you sure you want to delete\n{currentAnnouncement.Title}\nCreated at {currentAnnouncement.PublishDate.ToString("g")} by {currentAnnouncement.Author.Name}", + "Delete announcement", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) + { + AnnouncementManager announcementManager = new AnnouncementManager(); + announcementManager.DeleteAnnouncement(currentAnnouncement.ID); + } + RefreshLists(); + } + + private void btnEditAnnouncement_Click(object sender, EventArgs e) + { + AnnouncementForm announcementForm = new AnnouncementForm((Announcement)lbAnnouncements.SelectedItem, false, null); + announcementForm.ShowDialog(); + RefreshLists(); + } + + private void btnViewAnnouncement_Click(object sender, EventArgs e) + { + AnnouncementForm announcementForm = new AnnouncementForm((Announcement)lbAnnouncements.SelectedItem, true, null); + announcementForm.ShowDialog(); + RefreshLists(); + } } } diff --git a/StudentHouseDashboard/WinForms/WinForms.csproj b/StudentHouseDashboard/WinForms/WinForms.csproj index 9f2515e..c671066 100644 --- a/StudentHouseDashboard/WinForms/WinForms.csproj +++ b/StudentHouseDashboard/WinForms/WinForms.csproj @@ -12,4 +12,10 @@ + + + Form + + + \ No newline at end of file diff --git a/docs/dbdiagram.png b/docs/dbdiagram.png new file mode 100644 index 0000000000000000000000000000000000000000..47b23f52d988569bdbd76f171a08c00a75a0371e GIT binary patch literal 15024 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