Forráskód Böngészése

chore: update .gitignore

skipa605 3 hete
commit
3b6ada0c98

+ 41 - 0
.gitignore

@@ -0,0 +1,41 @@
+# Ignore compiled files
+bin/
+obj/
+packages/
+*.user
+*.suo
+
+# Ignore publish/installer output
+Output/
+InstallFiles/
+*.exe
+*.dll
+*.pdb
+*.xml
+!**/App.config
+!**/packages.config
+!**/FodyWeavers.xml
+
+# Ignore Visual Studio temp files
+.vs/
+*.csproj.user
+*.userprefs
+
+# Ignore ClickOnce publish output
+publish/
+*.application
+*.manifest
+
+# Ignore backup and temporary files
+*~
+*.swp
+.DS_Store
+
+# Ignore test results
+TestResults/
+coverage.coverage
+*.coverage.xml
+
+# Ignore key files (optional)
+*.pfx
+*.snk

+ 30 - 0
App.config

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
+    </startup>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-6.0.3.0" newVersion="6.0.3.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-8.0.0.5" newVersion="8.0.0.5" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
+      </dependentAssembly>
+      <dependentAssembly>
+        <assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-4.1.6.0" newVersion="4.1.6.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>

+ 352 - 0
AssignmentForm.Designer.cs

@@ -0,0 +1,352 @@
+namespace DeanOfficeApp
+{
+    partial class AssignmentForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+                components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
+            this.panelHeader = new Guna.UI2.WinForms.Guna2Panel();
+            this.lblTitle = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.panelContent = new Guna.UI2.WinForms.Guna2Panel();
+            this.txtSearch = new Guna.UI2.WinForms.Guna2TextBox();
+            this.btnRefresh = new Guna.UI2.WinForms.Guna2Button();
+            this.btnDelete = new Guna.UI2.WinForms.Guna2Button();
+            this.btnUpdate = new Guna.UI2.WinForms.Guna2Button();
+            this.btnAdd = new Guna.UI2.WinForms.Guna2Button();
+            this.cmbSemester = new Guna.UI2.WinForms.Guna2ComboBox();
+            this.cmbTeacher = new Guna.UI2.WinForms.Guna2ComboBox();
+            this.cmbDiscipline = new Guna.UI2.WinForms.Guna2ComboBox();
+            this.cmbGroup = new Guna.UI2.WinForms.Guna2ComboBox();
+            this.lblSemester = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.lblTeacher = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.lblDiscipline = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.lblGroup = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.dgvAssignments = new Guna.UI2.WinForms.Guna2DataGridView();
+            this.panelHeader.SuspendLayout();
+            this.panelContent.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvAssignments)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // panelHeader
+            // 
+            this.panelHeader.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(52)))), ((int)(((byte)(73)))), ((int)(((byte)(94)))));
+            this.panelHeader.Controls.Add(this.lblTitle);
+            this.panelHeader.Dock = System.Windows.Forms.DockStyle.Top;
+            this.panelHeader.Location = new System.Drawing.Point(0, 0);
+            this.panelHeader.Name = "panelHeader";
+            this.panelHeader.Size = new System.Drawing.Size(1000, 60);
+            this.panelHeader.TabIndex = 0;
+            // 
+            // lblTitle
+            // 
+            this.lblTitle.BackColor = System.Drawing.Color.Transparent;
+            this.lblTitle.Font = new System.Drawing.Font("Segoe UI", 18F, System.Drawing.FontStyle.Bold);
+            this.lblTitle.ForeColor = System.Drawing.Color.White;
+            this.lblTitle.Location = new System.Drawing.Point(20, 15);
+            this.lblTitle.Name = "lblTitle";
+            this.lblTitle.Size = new System.Drawing.Size(344, 34);
+            this.lblTitle.TabIndex = 0;
+            this.lblTitle.Text = "Редактирование расписания";
+            // 
+            // panelContent
+            // 
+            this.panelContent.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(242)))), ((int)(((byte)(245)))));
+            this.panelContent.Controls.Add(this.txtSearch);
+            this.panelContent.Controls.Add(this.btnRefresh);
+            this.panelContent.Controls.Add(this.btnDelete);
+            this.panelContent.Controls.Add(this.btnUpdate);
+            this.panelContent.Controls.Add(this.btnAdd);
+            this.panelContent.Controls.Add(this.cmbSemester);
+            this.panelContent.Controls.Add(this.cmbTeacher);
+            this.panelContent.Controls.Add(this.cmbDiscipline);
+            this.panelContent.Controls.Add(this.cmbGroup);
+            this.panelContent.Controls.Add(this.lblSemester);
+            this.panelContent.Controls.Add(this.lblTeacher);
+            this.panelContent.Controls.Add(this.lblDiscipline);
+            this.panelContent.Controls.Add(this.lblGroup);
+            this.panelContent.Controls.Add(this.dgvAssignments);
+            this.panelContent.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.panelContent.Location = new System.Drawing.Point(0, 60);
+            this.panelContent.Name = "panelContent";
+            this.panelContent.Size = new System.Drawing.Size(1000, 540);
+            this.panelContent.TabIndex = 1;
+            // 
+            // txtSearch
+            // 
+            this.txtSearch.Cursor = System.Windows.Forms.Cursors.IBeam;
+            this.txtSearch.DefaultText = "";
+            this.txtSearch.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.txtSearch.Location = new System.Drawing.Point(644, 464);
+            this.txtSearch.Name = "txtSearch";
+            this.txtSearch.PlaceholderText = "🔍 Поиск по группе, дисциплине, преподавателю...";
+            this.txtSearch.SelectedText = "";
+            this.txtSearch.Size = new System.Drawing.Size(220, 36);
+            this.txtSearch.TabIndex = 13;
+            this.txtSearch.TextChanged += new System.EventHandler(this.txtSearch_TextChanged);
+            // 
+            // btnRefresh
+            // 
+            this.btnRefresh.BorderRadius = 10;
+            this.btnRefresh.FillColor = System.Drawing.Color.FromArgb(((int)(((byte)(52)))), ((int)(((byte)(73)))), ((int)(((byte)(94)))));
+            this.btnRefresh.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
+            this.btnRefresh.ForeColor = System.Drawing.Color.White;
+            this.btnRefresh.Location = new System.Drawing.Point(870, 460);
+            this.btnRefresh.Name = "btnRefresh";
+            this.btnRefresh.Size = new System.Drawing.Size(110, 40);
+            this.btnRefresh.TabIndex = 12;
+            this.btnRefresh.Text = "Обновить";
+            this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click);
+            // 
+            // btnDelete
+            // 
+            this.btnDelete.BorderRadius = 10;
+            this.btnDelete.FillColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(53)))), ((int)(((byte)(69)))));
+            this.btnDelete.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
+            this.btnDelete.ForeColor = System.Drawing.Color.White;
+            this.btnDelete.Location = new System.Drawing.Point(280, 460);
+            this.btnDelete.Name = "btnDelete";
+            this.btnDelete.Size = new System.Drawing.Size(110, 40);
+            this.btnDelete.TabIndex = 11;
+            this.btnDelete.Text = "Удалить";
+            this.btnDelete.Click += new System.EventHandler(this.btnDelete_Click);
+            // 
+            // btnUpdate
+            // 
+            this.btnUpdate.BorderRadius = 10;
+            this.btnUpdate.FillColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(193)))), ((int)(((byte)(7)))));
+            this.btnUpdate.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
+            this.btnUpdate.ForeColor = System.Drawing.Color.Black;
+            this.btnUpdate.Location = new System.Drawing.Point(150, 460);
+            this.btnUpdate.Name = "btnUpdate";
+            this.btnUpdate.Size = new System.Drawing.Size(110, 40);
+            this.btnUpdate.TabIndex = 10;
+            this.btnUpdate.Text = "Обновить";
+            this.btnUpdate.Click += new System.EventHandler(this.btnUpdate_Click);
+            // 
+            // btnAdd
+            // 
+            this.btnAdd.BorderRadius = 10;
+            this.btnAdd.FillColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(167)))), ((int)(((byte)(69)))));
+            this.btnAdd.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
+            this.btnAdd.ForeColor = System.Drawing.Color.White;
+            this.btnAdd.Location = new System.Drawing.Point(20, 460);
+            this.btnAdd.Name = "btnAdd";
+            this.btnAdd.Size = new System.Drawing.Size(110, 40);
+            this.btnAdd.TabIndex = 9;
+            this.btnAdd.Text = "Добавить";
+            this.btnAdd.Click += new System.EventHandler(this.btnAdd_Click);
+            // 
+            // cmbSemester
+            // 
+            this.cmbSemester.BackColor = System.Drawing.Color.Transparent;
+            this.cmbSemester.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
+            this.cmbSemester.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbSemester.FocusedColor = System.Drawing.Color.FromArgb(((int)(((byte)(94)))), ((int)(((byte)(148)))), ((int)(((byte)(255)))));
+            this.cmbSemester.FocusedState.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(94)))), ((int)(((byte)(148)))), ((int)(((byte)(255)))));
+            this.cmbSemester.Font = new System.Drawing.Font("Segoe UI", 10F);
+            this.cmbSemester.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(68)))), ((int)(((byte)(88)))), ((int)(((byte)(112)))));
+            this.cmbSemester.ItemHeight = 30;
+            this.cmbSemester.Items.AddRange(new object[] {
+            "1",
+            "2",
+            "3",
+            "4",
+            "5",
+            "6",
+            "7",
+            "8"});
+            this.cmbSemester.Location = new System.Drawing.Point(680, 45);
+            this.cmbSemester.Name = "cmbSemester";
+            this.cmbSemester.Size = new System.Drawing.Size(120, 36);
+            this.cmbSemester.TabIndex = 7;
+            // 
+            // cmbTeacher
+            // 
+            this.cmbTeacher.BackColor = System.Drawing.Color.Transparent;
+            this.cmbTeacher.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
+            this.cmbTeacher.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbTeacher.FocusedColor = System.Drawing.Color.FromArgb(((int)(((byte)(94)))), ((int)(((byte)(148)))), ((int)(((byte)(255)))));
+            this.cmbTeacher.FocusedState.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(94)))), ((int)(((byte)(148)))), ((int)(((byte)(255)))));
+            this.cmbTeacher.Font = new System.Drawing.Font("Segoe UI", 10F);
+            this.cmbTeacher.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(68)))), ((int)(((byte)(88)))), ((int)(((byte)(112)))));
+            this.cmbTeacher.ItemHeight = 30;
+            this.cmbTeacher.Location = new System.Drawing.Point(460, 45);
+            this.cmbTeacher.Name = "cmbTeacher";
+            this.cmbTeacher.Size = new System.Drawing.Size(200, 36);
+            this.cmbTeacher.TabIndex = 5;
+            // 
+            // cmbDiscipline
+            // 
+            this.cmbDiscipline.BackColor = System.Drawing.Color.Transparent;
+            this.cmbDiscipline.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
+            this.cmbDiscipline.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbDiscipline.FocusedColor = System.Drawing.Color.FromArgb(((int)(((byte)(94)))), ((int)(((byte)(148)))), ((int)(((byte)(255)))));
+            this.cmbDiscipline.FocusedState.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(94)))), ((int)(((byte)(148)))), ((int)(((byte)(255)))));
+            this.cmbDiscipline.Font = new System.Drawing.Font("Segoe UI", 10F);
+            this.cmbDiscipline.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(68)))), ((int)(((byte)(88)))), ((int)(((byte)(112)))));
+            this.cmbDiscipline.ItemHeight = 30;
+            this.cmbDiscipline.Location = new System.Drawing.Point(240, 45);
+            this.cmbDiscipline.Name = "cmbDiscipline";
+            this.cmbDiscipline.Size = new System.Drawing.Size(200, 36);
+            this.cmbDiscipline.TabIndex = 3;
+            // 
+            // cmbGroup
+            // 
+            this.cmbGroup.BackColor = System.Drawing.Color.Transparent;
+            this.cmbGroup.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
+            this.cmbGroup.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbGroup.FocusedColor = System.Drawing.Color.FromArgb(((int)(((byte)(94)))), ((int)(((byte)(148)))), ((int)(((byte)(255)))));
+            this.cmbGroup.FocusedState.BorderColor = System.Drawing.Color.FromArgb(((int)(((byte)(94)))), ((int)(((byte)(148)))), ((int)(((byte)(255)))));
+            this.cmbGroup.Font = new System.Drawing.Font("Segoe UI", 10F);
+            this.cmbGroup.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(68)))), ((int)(((byte)(88)))), ((int)(((byte)(112)))));
+            this.cmbGroup.ItemHeight = 30;
+            this.cmbGroup.Location = new System.Drawing.Point(20, 45);
+            this.cmbGroup.Name = "cmbGroup";
+            this.cmbGroup.Size = new System.Drawing.Size(200, 36);
+            this.cmbGroup.TabIndex = 1;
+            // 
+            // lblSemester
+            // 
+            this.lblSemester.BackColor = System.Drawing.Color.Transparent;
+            this.lblSemester.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
+            this.lblSemester.Location = new System.Drawing.Point(680, 20);
+            this.lblSemester.Name = "lblSemester";
+            this.lblSemester.Size = new System.Drawing.Size(59, 19);
+            this.lblSemester.TabIndex = 6;
+            this.lblSemester.Text = "Семестр:";
+            // 
+            // lblTeacher
+            // 
+            this.lblTeacher.BackColor = System.Drawing.Color.Transparent;
+            this.lblTeacher.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
+            this.lblTeacher.Location = new System.Drawing.Point(460, 20);
+            this.lblTeacher.Name = "lblTeacher";
+            this.lblTeacher.Size = new System.Drawing.Size(106, 19);
+            this.lblTeacher.TabIndex = 4;
+            this.lblTeacher.Text = "Преподаватель:";
+            // 
+            // lblDiscipline
+            // 
+            this.lblDiscipline.BackColor = System.Drawing.Color.Transparent;
+            this.lblDiscipline.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
+            this.lblDiscipline.Location = new System.Drawing.Point(240, 20);
+            this.lblDiscipline.Name = "lblDiscipline";
+            this.lblDiscipline.Size = new System.Drawing.Size(86, 19);
+            this.lblDiscipline.TabIndex = 2;
+            this.lblDiscipline.Text = "Дисциплина:";
+            // 
+            // lblGroup
+            // 
+            this.lblGroup.BackColor = System.Drawing.Color.Transparent;
+            this.lblGroup.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
+            this.lblGroup.Location = new System.Drawing.Point(20, 20);
+            this.lblGroup.Name = "lblGroup";
+            this.lblGroup.Size = new System.Drawing.Size(52, 19);
+            this.lblGroup.TabIndex = 0;
+            this.lblGroup.Text = "Группа:";
+            // 
+            // dgvAssignments
+            // 
+            dataGridViewCellStyle1.BackColor = System.Drawing.Color.White;
+            this.dgvAssignments.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1;
+            dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle2.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(52)))), ((int)(((byte)(73)))), ((int)(((byte)(94)))));
+            dataGridViewCellStyle2.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
+            dataGridViewCellStyle2.ForeColor = System.Drawing.Color.White;
+            dataGridViewCellStyle2.SelectionBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(52)))), ((int)(((byte)(73)))), ((int)(((byte)(94)))));
+            dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
+            this.dgvAssignments.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2;
+            this.dgvAssignments.ColumnHeadersHeight = 40;
+            dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle3.BackColor = System.Drawing.Color.White;
+            dataGridViewCellStyle3.Font = new System.Drawing.Font("Segoe UI", 9F);
+            dataGridViewCellStyle3.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
+            dataGridViewCellStyle3.SelectionBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(229)))), ((int)(((byte)(241)))), ((int)(((byte)(251)))));
+            dataGridViewCellStyle3.SelectionForeColor = System.Drawing.Color.Black;
+            dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
+            this.dgvAssignments.DefaultCellStyle = dataGridViewCellStyle3;
+            this.dgvAssignments.GridColor = System.Drawing.Color.FromArgb(((int)(((byte)(230)))), ((int)(((byte)(230)))), ((int)(((byte)(230)))));
+            this.dgvAssignments.Location = new System.Drawing.Point(20, 100);
+            this.dgvAssignments.Name = "dgvAssignments";
+            this.dgvAssignments.ReadOnly = true;
+            this.dgvAssignments.RowHeadersVisible = false;
+            this.dgvAssignments.RowTemplate.Height = 30;
+            this.dgvAssignments.Size = new System.Drawing.Size(960, 340);
+            this.dgvAssignments.TabIndex = 8;
+            this.dgvAssignments.ThemeStyle.AlternatingRowsStyle.BackColor = System.Drawing.Color.White;
+            this.dgvAssignments.ThemeStyle.AlternatingRowsStyle.Font = null;
+            this.dgvAssignments.ThemeStyle.AlternatingRowsStyle.ForeColor = System.Drawing.Color.Empty;
+            this.dgvAssignments.ThemeStyle.AlternatingRowsStyle.SelectionBackColor = System.Drawing.Color.Empty;
+            this.dgvAssignments.ThemeStyle.AlternatingRowsStyle.SelectionForeColor = System.Drawing.Color.Empty;
+            this.dgvAssignments.ThemeStyle.BackColor = System.Drawing.Color.White;
+            this.dgvAssignments.ThemeStyle.GridColor = System.Drawing.Color.FromArgb(((int)(((byte)(230)))), ((int)(((byte)(230)))), ((int)(((byte)(230)))));
+            this.dgvAssignments.ThemeStyle.HeaderStyle.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(88)))), ((int)(((byte)(255)))));
+            this.dgvAssignments.ThemeStyle.HeaderStyle.BorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.None;
+            this.dgvAssignments.ThemeStyle.HeaderStyle.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
+            this.dgvAssignments.ThemeStyle.HeaderStyle.ForeColor = System.Drawing.Color.White;
+            this.dgvAssignments.ThemeStyle.HeaderStyle.HeaightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
+            this.dgvAssignments.ThemeStyle.HeaderStyle.Height = 40;
+            this.dgvAssignments.ThemeStyle.ReadOnly = true;
+            this.dgvAssignments.ThemeStyle.RowsStyle.BackColor = System.Drawing.Color.White;
+            this.dgvAssignments.ThemeStyle.RowsStyle.BorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.SingleHorizontal;
+            this.dgvAssignments.ThemeStyle.RowsStyle.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
+            this.dgvAssignments.ThemeStyle.RowsStyle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(71)))), ((int)(((byte)(69)))), ((int)(((byte)(94)))));
+            this.dgvAssignments.ThemeStyle.RowsStyle.Height = 30;
+            this.dgvAssignments.ThemeStyle.RowsStyle.SelectionBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(231)))), ((int)(((byte)(229)))), ((int)(((byte)(255)))));
+            this.dgvAssignments.ThemeStyle.RowsStyle.SelectionForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(71)))), ((int)(((byte)(69)))), ((int)(((byte)(94)))));
+            this.dgvAssignments.SelectionChanged += new System.EventHandler(this.dgvAssignments_SelectionChanged);
+            // 
+            // AssignmentForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(242)))), ((int)(((byte)(245)))));
+            this.ClientSize = new System.Drawing.Size(1000, 600);
+            this.Controls.Add(this.panelContent);
+            this.Controls.Add(this.panelHeader);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+            this.MaximizeBox = false;
+            this.Name = "AssignmentForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "Управление расписанием";
+            this.Load += new System.EventHandler(this.AssignmentForm_Load);
+            this.panelHeader.ResumeLayout(false);
+            this.panelHeader.PerformLayout();
+            this.panelContent.ResumeLayout(false);
+            this.panelContent.PerformLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvAssignments)).EndInit();
+            this.ResumeLayout(false);
+
+        }
+
+        // Поля компонентов (каждое объявлено ровно один раз)
+        private Guna.UI2.WinForms.Guna2Panel panelHeader;
+        private Guna.UI2.WinForms.Guna2HtmlLabel lblTitle;
+        private Guna.UI2.WinForms.Guna2Panel panelContent;
+        private Guna.UI2.WinForms.Guna2ComboBox cmbGroup;
+        private Guna.UI2.WinForms.Guna2ComboBox cmbDiscipline;
+        private Guna.UI2.WinForms.Guna2ComboBox cmbTeacher;
+        private Guna.UI2.WinForms.Guna2ComboBox cmbSemester;
+        private Guna.UI2.WinForms.Guna2HtmlLabel lblGroup;
+        private Guna.UI2.WinForms.Guna2HtmlLabel lblDiscipline;
+        private Guna.UI2.WinForms.Guna2HtmlLabel lblTeacher;
+        private Guna.UI2.WinForms.Guna2HtmlLabel lblSemester;
+        private Guna.UI2.WinForms.Guna2Button btnAdd;
+        private Guna.UI2.WinForms.Guna2Button btnUpdate;
+        private Guna.UI2.WinForms.Guna2Button btnDelete;
+        private Guna.UI2.WinForms.Guna2Button btnRefresh;
+        private Guna.UI2.WinForms.Guna2DataGridView dgvAssignments;
+        private Guna.UI2.WinForms.Guna2TextBox txtSearch;
+    }
+}

+ 397 - 0
AssignmentForm.cs

@@ -0,0 +1,397 @@
+using Guna.UI2.WinForms;
+using Npgsql;
+using System;
+using System.Data;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using System.Linq;
+
+namespace DeanOfficeApp
+{
+    public partial class AssignmentForm : Form
+    {
+        private readonly string _connString = "Host=245e1-rw.db.pub.dbaas.postgrespro.ru;Port=5432;Database=dbpractice;Username=mardasov_mv;Password=A4%2x$$2Ynr";
+        private DataTable _assignmentsTable;
+
+        public AssignmentForm()
+        {
+            InitializeComponent();
+            this.Load += AssignmentForm_Load;
+        }
+
+        private async void AssignmentForm_Load(object sender, EventArgs e)
+        {
+            await LoadAllDataAsync();
+        }
+
+        private async Task LoadAllDataAsync()
+        {
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                await Task.WhenAll(
+                    LoadGroupsAsync(),
+                    LoadDisciplinesAsync(),
+                    LoadTeachersAsync(),
+                    LoadAssignmentsAsync()
+                );
+            }
+            finally
+            {
+                Cursor = Cursors.Default;
+            }
+        }
+
+        private async Task LoadGroupsAsync()
+        {
+            try
+            {
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    string sql = "SELECT group_id, name FROM \"Group\" ORDER BY name";
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    using (var reader = await cmd.ExecuteReaderAsync())
+                    {
+                        var dt = new DataTable();
+                        dt.Load(reader);
+                        cmbGroup.DisplayMember = "name";
+                        cmbGroup.ValueMember = "group_id";
+                        cmbGroup.DataSource = dt;
+                        cmbGroup.SelectedIndex = -1;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки групп: {ex.Message}");
+            }
+        }
+
+        private async Task LoadDisciplinesAsync()
+        {
+            try
+            {
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    string sql = "SELECT discipline_id, name FROM Discipline ORDER BY name";
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    using (var reader = await cmd.ExecuteReaderAsync())
+                    {
+                        var dt = new DataTable();
+                        dt.Load(reader);
+                        cmbDiscipline.DisplayMember = "name";
+                        cmbDiscipline.ValueMember = "discipline_id";
+                        cmbDiscipline.DataSource = dt;
+                        cmbDiscipline.SelectedIndex = -1;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки дисциплин: {ex.Message}");
+            }
+        }
+
+        private async Task LoadTeachersAsync()
+        {
+            try
+            {
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    string sql = "SELECT teacher_id, full_name FROM Teacher ORDER BY full_name";
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    using (var reader = await cmd.ExecuteReaderAsync())
+                    {
+                        var dt = new DataTable();
+                        dt.Load(reader);
+                        cmbTeacher.DisplayMember = "full_name";
+                        cmbTeacher.ValueMember = "teacher_id";
+                        cmbTeacher.DataSource = dt;
+                        cmbTeacher.SelectedIndex = -1;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки преподавателей: {ex.Message}");
+            }
+        }
+
+        private async Task LoadAssignmentsAsync()
+        {
+            string sql = @"
+                SELECT a.assignment_id, g.name AS group_name, d.name AS discipline_name, 
+                       t.full_name AS teacher_name, a.semester
+                FROM Assignment a
+                JOIN ""Group"" g ON a.group_id = g.group_id
+                JOIN Discipline d ON a.discipline_id = d.discipline_id
+                JOIN Teacher t ON a.teacher_id = t.teacher_id
+                ORDER BY g.name, a.semester, d.name";
+
+            try
+            {
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    using (var reader = await cmd.ExecuteReaderAsync())
+                    {
+                        _assignmentsTable = new DataTable();
+                        _assignmentsTable.Load(reader);
+                        ApplyFilter();
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки назначений: {ex.Message}");
+            }
+        }
+
+        private void ApplyFilter()
+        {
+            if (_assignmentsTable == null) return;
+
+            string filterText = txtSearch.Text.Trim().ToLower();
+            if (string.IsNullOrEmpty(filterText))
+            {
+                dgvAssignments.DataSource = _assignmentsTable;
+            }
+            else
+            {
+                var filteredRows = _assignmentsTable.AsEnumerable()
+                    .Where(row => row["group_name"].ToString().ToLower().Contains(filterText) ||
+                                  row["discipline_name"].ToString().ToLower().Contains(filterText) ||
+                                  row["teacher_name"].ToString().ToLower().Contains(filterText) ||
+                                  row["semester"].ToString().Contains(filterText))
+                    .CopyToDataTable();
+                dgvAssignments.DataSource = filteredRows;
+            }
+
+            if (dgvAssignments.Columns.Contains("assignment_id"))
+                dgvAssignments.Columns["assignment_id"].Visible = false;
+        }
+
+        private async void btnAdd_Click(object sender, EventArgs e)
+        {
+            if (!ValidateInput()) return;
+
+            string sql = @"
+                INSERT INTO Assignment (group_id, discipline_id, teacher_id, semester)
+                VALUES (@g, @d, @t, @s)";
+
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@g", (int)cmbGroup.SelectedValue);
+                        cmd.Parameters.AddWithValue("@d", (int)cmbDiscipline.SelectedValue);
+                        cmd.Parameters.AddWithValue("@t", (int)cmbTeacher.SelectedValue);
+                        cmd.Parameters.AddWithValue("@s", (int)cmbSemester.SelectedItem);
+                        await cmd.ExecuteNonQueryAsync();
+                    }
+                }
+                await LoadAssignmentsAsync();
+                ClearFields();
+                MessageBox.Show("Назначение добавлено.");
+            }
+            catch (PostgresException ex) when (ex.SqlState == "23505")
+            {
+                MessageBox.Show("Такое назначение уже существует (группа + дисциплина + семестр).");
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+            finally { Cursor = Cursors.Default; }
+        }
+
+        private async void btnUpdate_Click(object sender, EventArgs e)
+        {
+            if (dgvAssignments.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите назначение для редактирования.");
+                return;
+            }
+            if (!ValidateInput()) return;
+
+            int assignmentId = (int)dgvAssignments.CurrentRow.Cells["assignment_id"].Value;
+
+            if (await HasGradesAsync(assignmentId))
+            {
+                MessageBox.Show("Нельзя изменить назначение, по которому уже есть оценки.", "Ограничение", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+                return;
+            }
+
+            string sql = @"
+                UPDATE Assignment 
+                SET group_id = @g, discipline_id = @d, teacher_id = @t, semester = @s
+                WHERE assignment_id = @id";
+
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@g", (int)cmbGroup.SelectedValue);
+                        cmd.Parameters.AddWithValue("@d", (int)cmbDiscipline.SelectedValue);
+                        cmd.Parameters.AddWithValue("@t", (int)cmbTeacher.SelectedValue);
+                        cmd.Parameters.AddWithValue("@s", (int)cmbSemester.SelectedItem);
+                        cmd.Parameters.AddWithValue("@id", assignmentId);
+                        await cmd.ExecuteNonQueryAsync();
+                    }
+                }
+                await LoadAssignmentsAsync();
+                ClearFields();
+                MessageBox.Show("Назначение обновлено.");
+            }
+            catch (PostgresException ex) when (ex.SqlState == "23505")
+            {
+                MessageBox.Show("Конфликт: такое назначение уже существует.");
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+            finally { Cursor = Cursors.Default; }
+        }
+
+        private async void btnDelete_Click(object sender, EventArgs e)
+        {
+            if (dgvAssignments.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите назначение для удаления.");
+                return;
+            }
+
+            int id = (int)dgvAssignments.CurrentRow.Cells["assignment_id"].Value;
+
+            if (await HasGradesAsync(id))
+            {
+                MessageBox.Show("Нельзя удалить назначение, по которому уже есть оценки.", "Ограничение", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+                return;
+            }
+
+            if (MessageBox.Show("Удалить назначение?", "Подтверждение", MessageBoxButtons.YesNo) != DialogResult.Yes)
+                return;
+
+            string sql = "DELETE FROM Assignment WHERE assignment_id = @id";
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@id", id);
+                        await cmd.ExecuteNonQueryAsync();
+                    }
+                }
+                await LoadAssignmentsAsync();
+                ClearFields();
+                MessageBox.Show("Назначение удалено.");
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+            finally { Cursor = Cursors.Default; }
+        }
+
+        private async Task<bool> HasGradesAsync(int assignmentId)
+        {
+            string sql = @"
+                SELECT COUNT(*) FROM Progress p
+                JOIN Assignment a ON p.discipline_id = a.discipline_id
+                WHERE a.assignment_id = @id";
+            try
+            {
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@id", assignmentId);
+                        long count = (long)await cmd.ExecuteScalarAsync();
+                        return count > 0;
+                    }
+                }
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
+        private async void btnRefresh_Click(object sender, EventArgs e)
+        {
+            await LoadAssignmentsAsync();
+            ClearFields();
+        }
+
+        private void txtSearch_TextChanged(object sender, EventArgs e)
+        {
+            ApplyFilter();
+        }
+
+        private void dgvAssignments_SelectionChanged(object sender, EventArgs e)
+        {
+            if (dgvAssignments.CurrentRow == null) return;
+
+            string groupName = dgvAssignments.CurrentRow.Cells["group_name"].Value?.ToString();
+            string disciplineName = dgvAssignments.CurrentRow.Cells["discipline_name"].Value?.ToString();
+            string teacherName = dgvAssignments.CurrentRow.Cells["teacher_name"].Value?.ToString();
+            int semester = Convert.ToInt32(dgvAssignments.CurrentRow.Cells["semester"].Value);
+
+            SelectComboBoxItemByText(cmbGroup, groupName);
+            SelectComboBoxItemByText(cmbDiscipline, disciplineName);
+            SelectComboBoxItemByText(cmbTeacher, teacherName);
+            cmbSemester.SelectedItem = semester.ToString();
+        }
+
+        private void SelectComboBoxItemByText(Guna2ComboBox comboBox, string text)
+        {
+            if (string.IsNullOrEmpty(text) || comboBox.DataSource == null) return;
+            foreach (DataRowView row in comboBox.Items)
+            {
+                string displayText = row[comboBox.DisplayMember].ToString();
+                if (displayText == text)
+                {
+                    comboBox.SelectedItem = row;
+                    break;
+                }
+            }
+        }
+
+        private bool ValidateInput()
+        {
+            if (cmbGroup.SelectedValue == null || cmbDiscipline.SelectedValue == null ||
+                cmbTeacher.SelectedValue == null || cmbSemester.SelectedItem == null)
+            {
+                MessageBox.Show("Заполните все поля (группа, дисциплина, преподаватель, семестр).");
+                return false;
+            }
+            return true;
+        }
+
+        private void ClearFields()
+        {
+            cmbGroup.SelectedIndex = -1;
+            cmbDiscipline.SelectedIndex = -1;
+            cmbTeacher.SelectedIndex = -1;
+            cmbSemester.SelectedIndex = 0;
+            txtSearch.Clear();
+        }
+    }
+}

+ 120 - 0
AssignmentForm.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 231 - 0
DeanOfficeApp.csproj

@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="..\packages\Costura.Fody.6.1.0\build\Costura.Fody.props" Condition="Exists('..\packages\Costura.Fody.6.1.0\build\Costura.Fody.props')" />
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{80C1DCB0-9785-452A-846D-FAF2EC6C1CD2}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>ConsoleApp4</RootNamespace>
+    <AssemblyName>ConsoleApp4</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+    <IsWebBootstrapper>false</IsWebBootstrapper>
+    <PublishUrl>publish\</PublishUrl>
+    <Install>true</Install>
+    <InstallFrom>Disk</InstallFrom>
+    <UpdateEnabled>false</UpdateEnabled>
+    <UpdateMode>Foreground</UpdateMode>
+    <UpdateInterval>7</UpdateInterval>
+    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+    <UpdatePeriodically>false</UpdatePeriodically>
+    <UpdateRequired>false</UpdateRequired>
+    <MapFileExtensions>true</MapFileExtensions>
+    <ApplicationRevision>1</ApplicationRevision>
+    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+    <UseApplicationTrust>false</UseApplicationTrust>
+    <PublishWizardCompleted>true</PublishWizardCompleted>
+    <BootstrapperEnabled>true</BootstrapperEnabled>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ManifestCertificateThumbprint>384A3F397BBB2B96D0556F8733BE94B8B1C1149C</ManifestCertificateThumbprint>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ManifestKeyFile>DeanOfficeApp_TemporaryKey.pfx</ManifestKeyFile>
+  </PropertyGroup>
+  <PropertyGroup>
+    <GenerateManifests>false</GenerateManifests>
+  </PropertyGroup>
+  <PropertyGroup>
+    <SignManifests>true</SignManifests>
+  </PropertyGroup>
+  <PropertyGroup>
+    <TargetZone>LocalIntranet</TargetZone>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationManifest>Properties\app.manifest</ApplicationManifest>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="BCrypt-Net-Next, Version=4.2.0.0, Culture=neutral, PublicKeyToken=1e11be04b6288443, processorArchitecture=MSIL">
+      <HintPath>..\packages\BCrypt.Net-Next.4.2.0\lib\net472\BCrypt-Net-Next.dll</HintPath>
+    </Reference>
+    <Reference Include="Costura, Version=6.1.0.0, Culture=neutral, PublicKeyToken=9919ef960d84173d, processorArchitecture=MSIL">
+      <HintPath>..\packages\Costura.Fody.6.1.0\lib\netstandard2.0\Costura.dll</HintPath>
+    </Reference>
+    <Reference Include="CsvHelper, Version=33.0.0.0, Culture=neutral, PublicKeyToken=8c4959082be5c823, processorArchitecture=MSIL">
+      <HintPath>..\packages\CsvHelper.33.1.0\lib\net47\CsvHelper.dll</HintPath>
+    </Reference>
+    <Reference Include="Guna.UI2, Version=2.0.4.7, Culture=neutral, PublicKeyToken=8b9d14aa5142e261, processorArchitecture=MSIL">
+      <HintPath>..\packages\Guna.UI2.WinForms.2.0.4.7\lib\net472\Guna.UI2.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Bcl.HashCode, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Bcl.HashCode.1.1.1\lib\net461\Microsoft.Bcl.HashCode.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.8.0.0\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
+      <HintPath>..\packages\Microsoft.Extensions.Logging.Abstractions.8.0.0\lib\net462\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
+    </Reference>
+    <Reference Include="Npgsql, Version=8.0.5.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7, processorArchitecture=MSIL">
+      <HintPath>..\packages\Npgsql.8.0.5\lib\netstandard2.0\Npgsql.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Buffers, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Collections.Immutable, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Collections.Immutable.8.0.0\lib\net462\System.Collections.Immutable.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Core" />
+    <Reference Include="System.Design" />
+    <Reference Include="System.Diagnostics.DiagnosticSource, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Diagnostics.DiagnosticSource.8.0.0\lib\net462\System.Diagnostics.DiagnosticSource.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Drawing" />
+    <Reference Include="System.Management" />
+    <Reference Include="System.Memory, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Numerics" />
+    <Reference Include="System.Numerics.Vectors, Version=4.1.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Text.Encodings.Web, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Text.Encodings.Web.8.0.0\lib\net462\System.Text.Encodings.Web.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Text.Json, Version=8.0.0.5, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Text.Json.8.0.5\lib\net462\System.Text.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Threading.Channels, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Threading.Channels.8.0.0\lib\net462\System.Threading.Channels.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
+    </Reference>
+    <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Web" />
+    <Reference Include="System.Windows.Forms" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="AssignmentForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="AssignmentForm.Designer.cs">
+      <DependentUpon>AssignmentForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="GradeForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="GradeForm.Designer.cs">
+      <DependentUpon>GradeForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="LoginForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="LoginForm.Designer.cs">
+      <DependentUpon>LoginForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="MainForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="MainForm.Designer.cs">
+      <DependentUpon>MainForm.cs</DependentUpon>
+    </Compile>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="StudentForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+    <Compile Include="UserManagementForm.cs">
+      <SubType>Form</SubType>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+    <None Include="DeanOfficeApp_TemporaryKey.pfx" />
+    <None Include="packages.config" />
+    <None Include="Properties\app.manifest" />
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="AssignmentForm.resx">
+      <DependentUpon>AssignmentForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="GradeForm.resx">
+      <DependentUpon>GradeForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="LoginForm.resx">
+      <DependentUpon>LoginForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="MainForm.resx">
+      <DependentUpon>MainForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="StudentForm.resx">
+      <DependentUpon>StudentForm.cs</DependentUpon>
+    </EmbeddedResource>
+    <EmbeddedResource Include="UserManagementForm.resx">
+      <DependentUpon>UserManagementForm.cs</DependentUpon>
+    </EmbeddedResource>
+  </ItemGroup>
+  <ItemGroup>
+    <BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
+      <Visible>False</Visible>
+      <ProductName>Microsoft .NET Framework 4.7.2 %28x86 и x64%29</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="..\packages\Fody.6.9.3\build\Fody.targets" Condition="Exists('..\packages\Fody.6.9.3\build\Fody.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>Данный проект ссылается на пакеты NuGet, отсутствующие на этом компьютере. Используйте восстановление пакетов NuGet, чтобы скачать их.  Дополнительную информацию см. по адресу: http://go.microsoft.com/fwlink/?LinkID=322105. Отсутствует следующий файл: {0}.</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('..\packages\Fody.6.9.3\build\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.6.9.3\build\Fody.targets'))" />
+    <Error Condition="!Exists('..\packages\Costura.Fody.6.1.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.6.1.0\build\Costura.Fody.props'))" />
+    <Error Condition="!Exists('..\packages\Costura.Fody.6.1.0\build\Costura.Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.6.1.0\build\Costura.Fody.targets'))" />
+  </Target>
+  <Import Project="..\packages\Costura.Fody.6.1.0\build\Costura.Fody.targets" Condition="Exists('..\packages\Costura.Fody.6.1.0\build\Costura.Fody.targets')" />
+</Project>

BIN
DeanOfficeApp_TemporaryKey.pfx


+ 3 - 0
FodyWeavers.xml

@@ -0,0 +1,3 @@
+<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
+  <Costura />
+</Weavers>

+ 186 - 0
FodyWeavers.xsd

@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+  <!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
+  <xs:element name="Weavers">
+    <xs:complexType>
+      <xs:all>
+        <xs:element name="Costura" minOccurs="0" maxOccurs="1">
+          <xs:complexType>
+            <xs:all>
+              <xs:element minOccurs="0" maxOccurs="1" name="ExcludeAssemblies" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="IncludeAssemblies" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="ExcludeRuntimeAssemblies" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="IncludeRuntimeAssemblies" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="ExcludeRuntimes" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>A list of runtimes to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="IncludeRuntimes" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>A list of runtimes names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="Unmanaged32Assemblies" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinX86Assemblies" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with line breaks.</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="Unmanaged64Assemblies" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead.</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinX64Assemblies" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinArm64Assemblies" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+              <xs:element minOccurs="0" maxOccurs="1" name="PreloadOrder" type="xs:string">
+                <xs:annotation>
+                  <xs:documentation>The order of preloaded assemblies, delimited with line breaks.</xs:documentation>
+                </xs:annotation>
+              </xs:element>
+            </xs:all>
+            <xs:attribute name="CreateTemporaryAssemblies" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="IncludeDebugSymbols" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>Controls if .pdbs for reference assemblies are also embedded.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="IncludeRuntimeReferences" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>Controls if runtime assemblies are also embedded.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="UseRuntimeReferencePaths" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>Controls whether the runtime assemblies are embedded with their full path or only with their assembly name.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="DisableCompression" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="DisableCleanup" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="DisableEventSubscription" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>The attach method no longer subscribes to the `AppDomain.AssemblyResolve` (.NET 4.x) and `AssemblyLoadContext.Resolving` (.NET 6.0+) events.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="LoadAtModuleInit" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="IgnoreSatelliteAssemblies" type="xs:boolean">
+              <xs:annotation>
+                <xs:documentation>Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="ExcludeAssemblies" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="IncludeAssemblies" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="ExcludeRuntimeAssemblies" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="IncludeRuntimeAssemblies" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="Unmanaged32Assemblies" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="UnmanagedWinX86Assemblies" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with |.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="Unmanaged64Assemblies" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="UnmanagedWinX64Assemblies" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with |.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="UnmanagedWinArm64Assemblies" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with |.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+            <xs:attribute name="PreloadOrder" type="xs:string">
+              <xs:annotation>
+                <xs:documentation>The order of preloaded assemblies, delimited with |.</xs:documentation>
+              </xs:annotation>
+            </xs:attribute>
+          </xs:complexType>
+        </xs:element>
+      </xs:all>
+      <xs:attribute name="VerifyAssembly" type="xs:boolean">
+        <xs:annotation>
+          <xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="VerifyIgnoreCodes" type="xs:string">
+        <xs:annotation>
+          <xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
+        </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="GenerateXsd" type="xs:boolean">
+        <xs:annotation>
+          <xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
+        </xs:annotation>
+      </xs:attribute>
+    </xs:complexType>
+  </xs:element>
+</xs:schema>

+ 301 - 0
GradeForm.Designer.cs

@@ -0,0 +1,301 @@
+namespace DeanOfficeApp
+{
+    partial class GradeForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+                components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
+            this.panelHeader = new Guna.UI2.WinForms.Guna2Panel();
+            this.lblTitle = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.panelContent = new Guna.UI2.WinForms.Guna2Panel();
+            this.txtSearchStudent = new Guna.UI2.WinForms.Guna2TextBox();
+            this.btnRefresh = new Guna.UI2.WinForms.Guna2Button();
+            this.btnSave = new Guna.UI2.WinForms.Guna2Button();
+            this.dtpExamDate = new Guna.UI2.WinForms.Guna2DateTimePicker();
+            this.nudGrade = new Guna.UI2.WinForms.Guna2NumericUpDown();
+            this.lblGrade = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.lblDate = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.dgvStudents = new Guna.UI2.WinForms.Guna2DataGridView();
+            this.cmbSemester = new Guna.UI2.WinForms.Guna2ComboBox();
+            this.cmbDiscipline = new Guna.UI2.WinForms.Guna2ComboBox();
+            this.cmbGroup = new Guna.UI2.WinForms.Guna2ComboBox();
+            this.lblSemester = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.lblDiscipline = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.lblGroup = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.panelHeader.SuspendLayout();
+            this.panelContent.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.nudGrade)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvStudents)).BeginInit();
+            this.SuspendLayout();
+
+            // panelHeader
+            this.panelHeader.BackColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            this.panelHeader.Controls.Add(this.lblTitle);
+            this.panelHeader.Dock = System.Windows.Forms.DockStyle.Top;
+            this.panelHeader.Location = new System.Drawing.Point(0, 0);
+            this.panelHeader.Name = "panelHeader";
+            this.panelHeader.Size = new System.Drawing.Size(900, 60);
+            this.panelHeader.TabIndex = 0;
+
+            // lblTitle
+            this.lblTitle.BackColor = System.Drawing.Color.Transparent;
+            this.lblTitle.Font = new System.Drawing.Font("Segoe UI", 18F, System.Drawing.FontStyle.Bold);
+            this.lblTitle.ForeColor = System.Drawing.Color.White;
+            this.lblTitle.Location = new System.Drawing.Point(20, 15);
+            this.lblTitle.Name = "lblTitle";
+            this.lblTitle.Size = new System.Drawing.Size(283, 34);
+            this.lblTitle.TabIndex = 0;
+            this.lblTitle.Text = "Успеваемость / Оценки";
+
+            // panelContent
+            this.panelContent.BackColor = System.Drawing.Color.FromArgb(240, 242, 245);
+            this.panelContent.Controls.Add(this.txtSearchStudent);
+            this.panelContent.Controls.Add(this.btnRefresh);
+            this.panelContent.Controls.Add(this.btnSave);
+            this.panelContent.Controls.Add(this.dtpExamDate);
+            this.panelContent.Controls.Add(this.nudGrade);
+            this.panelContent.Controls.Add(this.lblGrade);
+            this.panelContent.Controls.Add(this.lblDate);
+            this.panelContent.Controls.Add(this.dgvStudents);
+            this.panelContent.Controls.Add(this.cmbSemester);
+            this.panelContent.Controls.Add(this.cmbDiscipline);
+            this.panelContent.Controls.Add(this.cmbGroup);
+            this.panelContent.Controls.Add(this.lblSemester);
+            this.panelContent.Controls.Add(this.lblDiscipline);
+            this.panelContent.Controls.Add(this.lblGroup);
+            this.panelContent.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.panelContent.Location = new System.Drawing.Point(0, 60);
+            this.panelContent.Name = "panelContent";
+            this.panelContent.Size = new System.Drawing.Size(900, 540);
+            this.panelContent.TabIndex = 1;
+
+            // txtSearchStudent
+            this.txtSearchStudent.Cursor = System.Windows.Forms.Cursors.IBeam;
+            this.txtSearchStudent.DefaultText = "";
+            this.txtSearchStudent.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.txtSearchStudent.Location = new System.Drawing.Point(640, 480);
+            this.txtSearchStudent.Name = "txtSearchStudent";
+            this.txtSearchStudent.PlaceholderText = "🔍 Поиск по фамилии студента...";
+            this.txtSearchStudent.Size = new System.Drawing.Size(240, 36);
+            this.txtSearchStudent.TabIndex = 13;
+            this.txtSearchStudent.TextChanged += new System.EventHandler(this.txtSearchStudent_TextChanged);
+
+            // btnRefresh
+            this.btnRefresh.BorderRadius = 10;
+            this.btnRefresh.FillColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            this.btnRefresh.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
+            this.btnRefresh.ForeColor = System.Drawing.Color.White;
+            this.btnRefresh.Location = new System.Drawing.Point(500, 480);
+            this.btnRefresh.Name = "btnRefresh";
+            this.btnRefresh.Size = new System.Drawing.Size(110, 40);
+            this.btnRefresh.TabIndex = 12;
+            this.btnRefresh.Text = "Обновить";
+            this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click);
+
+            // btnSave (без изменений)
+            this.btnSave.BorderRadius = 10;
+            this.btnSave.FillColor = System.Drawing.Color.FromArgb(40, 167, 69);
+            this.btnSave.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
+            this.btnSave.ForeColor = System.Drawing.Color.White;
+            this.btnSave.Location = new System.Drawing.Point(330, 480);
+            this.btnSave.Name = "btnSave";
+            this.btnSave.Size = new System.Drawing.Size(130, 40);
+            this.btnSave.TabIndex = 11;
+            this.btnSave.Text = "Сохранить";
+            this.btnSave.Click += new System.EventHandler(this.btnSave_Click);
+
+            // dtpExamDate (без изменений)
+            this.dtpExamDate.BorderRadius = 8;
+            this.dtpExamDate.Checked = true;
+            this.dtpExamDate.FillColor = System.Drawing.Color.White;
+            this.dtpExamDate.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.dtpExamDate.Format = System.Windows.Forms.DateTimePickerFormat.Short;
+            this.dtpExamDate.Location = new System.Drawing.Point(130, 485);
+            this.dtpExamDate.MaxDate = new System.DateTime(2030, 12, 31, 0, 0, 0, 0);
+            this.dtpExamDate.MinDate = new System.DateTime(2020, 1, 1, 0, 0, 0, 0);
+            this.dtpExamDate.Name = "dtpExamDate";
+            this.dtpExamDate.Size = new System.Drawing.Size(150, 36);
+            this.dtpExamDate.TabIndex = 10;
+            this.dtpExamDate.Value = new System.DateTime(2025, 6, 12, 0, 0, 0, 0);
+
+            // nudGrade (без изменений)
+            this.nudGrade.BackColor = System.Drawing.Color.Transparent;
+            this.nudGrade.Cursor = System.Windows.Forms.Cursors.IBeam;
+            this.nudGrade.Font = new System.Drawing.Font("Segoe UI", 10F);
+            this.nudGrade.Location = new System.Drawing.Point(20, 485);
+            this.nudGrade.Maximum = 5;
+            this.nudGrade.Minimum = 2;
+            this.nudGrade.Name = "nudGrade";
+            this.nudGrade.Size = new System.Drawing.Size(80, 36);
+            this.nudGrade.TabIndex = 8;
+            this.nudGrade.Value = 2;
+
+            // lblGrade
+            this.lblGrade.BackColor = System.Drawing.Color.Transparent;
+            this.lblGrade.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
+            this.lblGrade.Location = new System.Drawing.Point(20, 460);
+            this.lblGrade.Name = "lblGrade";
+            this.lblGrade.Size = new System.Drawing.Size(54, 19);
+            this.lblGrade.TabIndex = 7;
+            this.lblGrade.Text = "Оценка:";
+
+            // lblDate
+            this.lblDate.BackColor = System.Drawing.Color.Transparent;
+            this.lblDate.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
+            this.lblDate.Location = new System.Drawing.Point(130, 460);
+            this.lblDate.Name = "lblDate";
+            this.lblDate.Size = new System.Drawing.Size(78, 19);
+            this.lblDate.TabIndex = 9;
+            this.lblDate.Text = "Дата сдачи:";
+
+            // dgvStudents (без изменений)
+            dataGridViewCellStyle1.BackColor = System.Drawing.Color.White;
+            this.dgvStudents.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1;
+            dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle2.BackColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            dataGridViewCellStyle2.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
+            dataGridViewCellStyle2.ForeColor = System.Drawing.Color.White;
+            dataGridViewCellStyle2.SelectionBackColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
+            this.dgvStudents.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2;
+            this.dgvStudents.ColumnHeadersHeight = 40;
+            dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle3.BackColor = System.Drawing.Color.White;
+            dataGridViewCellStyle3.Font = new System.Drawing.Font("Segoe UI", 9F);
+            dataGridViewCellStyle3.ForeColor = System.Drawing.Color.FromArgb(64, 64, 64);
+            dataGridViewCellStyle3.SelectionBackColor = System.Drawing.Color.FromArgb(229, 241, 251);
+            dataGridViewCellStyle3.SelectionForeColor = System.Drawing.Color.Black;
+            dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
+            this.dgvStudents.DefaultCellStyle = dataGridViewCellStyle3;
+            this.dgvStudents.GridColor = System.Drawing.Color.FromArgb(230, 230, 230);
+            this.dgvStudents.Location = new System.Drawing.Point(20, 100);
+            this.dgvStudents.Name = "dgvStudents";
+            this.dgvStudents.ReadOnly = true;
+            this.dgvStudents.RowHeadersVisible = false;
+            this.dgvStudents.RowTemplate.Height = 30;
+            this.dgvStudents.Size = new System.Drawing.Size(860, 340);
+            this.dgvStudents.TabIndex = 6;
+            this.dgvStudents.SelectionChanged += new System.EventHandler(this.dgvStudents_SelectionChanged);
+
+            // cmbSemester
+            this.cmbSemester.BackColor = System.Drawing.Color.Transparent;
+            this.cmbSemester.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
+            this.cmbSemester.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbSemester.FocusedColor = System.Drawing.Color.FromArgb(94, 148, 255);
+            this.cmbSemester.Font = new System.Drawing.Font("Segoe UI", 10F);
+            this.cmbSemester.ForeColor = System.Drawing.Color.FromArgb(68, 88, 112);
+            this.cmbSemester.ItemHeight = 30;
+            this.cmbSemester.Items.AddRange(new object[] { "1", "2", "3", "4", "5", "6", "7", "8" });
+            this.cmbSemester.Location = new System.Drawing.Point(580, 45);
+            this.cmbSemester.Name = "cmbSemester";
+            this.cmbSemester.Size = new System.Drawing.Size(120, 36);
+            this.cmbSemester.TabIndex = 5;
+            this.cmbSemester.SelectedIndexChanged += new System.EventHandler(this.cmbSemester_SelectedIndexChanged);
+
+            // cmbDiscipline
+            this.cmbDiscipline.BackColor = System.Drawing.Color.Transparent;
+            this.cmbDiscipline.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
+            this.cmbDiscipline.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbDiscipline.FocusedColor = System.Drawing.Color.FromArgb(94, 148, 255);
+            this.cmbDiscipline.Font = new System.Drawing.Font("Segoe UI", 10F);
+            this.cmbDiscipline.ForeColor = System.Drawing.Color.FromArgb(68, 88, 112);
+            this.cmbDiscipline.ItemHeight = 30;
+            this.cmbDiscipline.Location = new System.Drawing.Point(300, 45);
+            this.cmbDiscipline.Name = "cmbDiscipline";
+            this.cmbDiscipline.Size = new System.Drawing.Size(250, 36);
+            this.cmbDiscipline.TabIndex = 3;
+            this.cmbDiscipline.SelectedIndexChanged += new System.EventHandler(this.cmbDiscipline_SelectedIndexChanged);
+
+            // cmbGroup
+            this.cmbGroup.BackColor = System.Drawing.Color.Transparent;
+            this.cmbGroup.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
+            this.cmbGroup.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbGroup.FocusedColor = System.Drawing.Color.FromArgb(94, 148, 255);
+            this.cmbGroup.Font = new System.Drawing.Font("Segoe UI", 10F);
+            this.cmbGroup.ForeColor = System.Drawing.Color.FromArgb(68, 88, 112);
+            this.cmbGroup.ItemHeight = 30;
+            this.cmbGroup.Location = new System.Drawing.Point(20, 45);
+            this.cmbGroup.Name = "cmbGroup";
+            this.cmbGroup.Size = new System.Drawing.Size(250, 36);
+            this.cmbGroup.TabIndex = 1;
+            this.cmbGroup.SelectedIndexChanged += new System.EventHandler(this.cmbGroup_SelectedIndexChanged);
+
+            // lblSemester
+            this.lblSemester.BackColor = System.Drawing.Color.Transparent;
+            this.lblSemester.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
+            this.lblSemester.Location = new System.Drawing.Point(580, 20);
+            this.lblSemester.Name = "lblSemester";
+            this.lblSemester.Size = new System.Drawing.Size(59, 19);
+            this.lblSemester.TabIndex = 4;
+            this.lblSemester.Text = "Семестр:";
+
+            // lblDiscipline
+            this.lblDiscipline.BackColor = System.Drawing.Color.Transparent;
+            this.lblDiscipline.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
+            this.lblDiscipline.Location = new System.Drawing.Point(300, 20);
+            this.lblDiscipline.Name = "lblDiscipline";
+            this.lblDiscipline.Size = new System.Drawing.Size(86, 19);
+            this.lblDiscipline.TabIndex = 2;
+            this.lblDiscipline.Text = "Дисциплина:";
+
+            // lblGroup
+            this.lblGroup.BackColor = System.Drawing.Color.Transparent;
+            this.lblGroup.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
+            this.lblGroup.Location = new System.Drawing.Point(20, 20);
+            this.lblGroup.Name = "lblGroup";
+            this.lblGroup.Size = new System.Drawing.Size(52, 19);
+            this.lblGroup.TabIndex = 0;
+            this.lblGroup.Text = "Группа:";
+
+            // GradeForm
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.BackColor = System.Drawing.Color.FromArgb(240, 242, 245);
+            this.ClientSize = new System.Drawing.Size(900, 600);
+            this.Controls.Add(this.panelContent);
+            this.Controls.Add(this.panelHeader);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+            this.MaximizeBox = false;
+            this.Name = "GradeForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "Управление успеваемостью";
+            this.panelHeader.ResumeLayout(false);
+            this.panelHeader.PerformLayout();
+            this.panelContent.ResumeLayout(false);
+            this.panelContent.PerformLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.nudGrade)).EndInit();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvStudents)).EndInit();
+            this.ResumeLayout(false);
+        }
+
+        // Поля компонентов
+        private Guna.UI2.WinForms.Guna2Panel panelHeader;
+        private Guna.UI2.WinForms.Guna2HtmlLabel lblTitle;
+        private Guna.UI2.WinForms.Guna2Panel panelContent;
+        private Guna.UI2.WinForms.Guna2ComboBox cmbGroup;
+        private Guna.UI2.WinForms.Guna2ComboBox cmbDiscipline;
+        private Guna.UI2.WinForms.Guna2ComboBox cmbSemester;
+        private Guna.UI2.WinForms.Guna2DataGridView dgvStudents;
+        private Guna.UI2.WinForms.Guna2HtmlLabel lblGroup;
+        private Guna.UI2.WinForms.Guna2HtmlLabel lblDiscipline;
+        private Guna.UI2.WinForms.Guna2HtmlLabel lblSemester;
+        private Guna.UI2.WinForms.Guna2HtmlLabel lblGrade;
+        private Guna.UI2.WinForms.Guna2HtmlLabel lblDate;
+        private Guna.UI2.WinForms.Guna2NumericUpDown nudGrade;
+        private Guna.UI2.WinForms.Guna2DateTimePicker dtpExamDate;
+        private Guna.UI2.WinForms.Guna2Button btnSave;
+        private Guna.UI2.WinForms.Guna2Button btnRefresh;      // добавлено
+        private Guna.UI2.WinForms.Guna2TextBox txtSearchStudent; // добавлено
+    }
+}

+ 306 - 0
GradeForm.cs

@@ -0,0 +1,306 @@
+using Guna.UI2.WinForms;
+using Npgsql;
+using System;
+using System.Data;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace DeanOfficeApp
+{
+    public partial class GradeForm : Form
+    {
+        private readonly string _connString = "Host=245e1-rw.db.pub.dbaas.postgrespro.ru;Port=5432;Database=dbpractice;Username=mardasov_mv;Password=A4%2x$$2Ynr";
+        private DataTable _studentsTable;
+
+        public GradeForm()
+        {
+            InitializeComponent();
+            this.Load += async (s, e) => await LoadAllDataAsync();
+        }
+
+        private async Task LoadAllDataAsync()
+        {
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                await Task.WhenAll(LoadGroupsAsync(), LoadDisciplinesAsync());
+                cmbSemester.SelectedIndex = 0;
+            }
+            finally
+            {
+                Cursor = Cursors.Default;
+            }
+        }
+
+        private async Task LoadGroupsAsync()
+        {
+            try
+            {
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    string sql = "SELECT group_id, name FROM \"Group\" ORDER BY name";
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    using (var reader = await cmd.ExecuteReaderAsync())
+                    {
+                        var dt = new DataTable();
+                        dt.Load(reader);
+                        cmbGroup.DisplayMember = "name";
+                        cmbGroup.ValueMember = "group_id";
+                        cmbGroup.DataSource = dt;
+                        cmbGroup.SelectedIndex = -1;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки групп: {ex.Message}");
+            }
+        }
+
+        private async Task LoadDisciplinesAsync()
+        {
+            try
+            {
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    string sql = "SELECT discipline_id, name FROM Discipline ORDER BY name";
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    using (var reader = await cmd.ExecuteReaderAsync())
+                    {
+                        var dt = new DataTable();
+                        dt.Load(reader);
+                        cmbDiscipline.DisplayMember = "name";
+                        cmbDiscipline.ValueMember = "discipline_id";
+                        cmbDiscipline.DataSource = dt;
+                        cmbDiscipline.SelectedIndex = -1;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки дисциплин: {ex.Message}");
+            }
+        }
+
+        private async Task LoadStudentsAndGradesAsync()
+        {
+            if (cmbGroup.SelectedValue == null || cmbDiscipline.SelectedValue == null)
+                return;
+
+            int groupId = (int)cmbGroup.SelectedValue;
+            int disciplineId = (int)cmbDiscipline.SelectedValue;
+
+            string sql = @"
+                SELECT 
+                    s.record_book,
+                    s.full_name,
+                    COALESCE(p.grade, 0) AS grade,
+                    p.exam_date
+                FROM Student s
+                LEFT JOIN Progress p ON s.record_book = p.record_book 
+                    AND p.discipline_id = @discId
+                WHERE s.group_id = @gid
+                ORDER BY s.full_name";
+
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@discId", disciplineId);
+                        cmd.Parameters.AddWithValue("@gid", groupId);
+                        using (var reader = await cmd.ExecuteReaderAsync())
+                        {
+                            var dt = new DataTable();
+                            dt.Load(reader);
+                            _studentsTable = dt;
+                            ApplyFilter(); // применяем фильтр поиска
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки успеваемости: {ex.Message}");
+            }
+            finally
+            {
+                Cursor = Cursors.Default;
+            }
+        }
+
+        // Фильтрация таблицы студентов по тексту поиска
+        private void ApplyFilter()
+        {
+            if (_studentsTable == null) return;
+
+            string filterText = txtSearchStudent.Text.Trim().ToLower();
+            if (string.IsNullOrEmpty(filterText))
+            {
+                dgvStudents.DataSource = _studentsTable;
+            }
+            else
+            {
+                var filteredRows = _studentsTable.AsEnumerable()
+                    .Where(row => row["full_name"].ToString().ToLower().Contains(filterText))
+                    .CopyToDataTable();
+                dgvStudents.DataSource = filteredRows;
+            }
+
+            if (dgvStudents.Columns.Contains("record_book"))
+                dgvStudents.Columns["record_book"].Visible = false;
+            dgvStudents.Columns["grade"].HeaderText = "Оценка";
+            dgvStudents.Columns["exam_date"].HeaderText = "Дата сдачи";
+            dgvStudents.Columns["exam_date"].DefaultCellStyle.Format = "dd.MM.yyyy";
+        }
+
+        private async void btnSave_Click(object sender, EventArgs e)
+        {
+            if (dgvStudents.CurrentRow == null) return;
+
+            // Проверка наличия назначения (Assignment) для выбранной группы, дисциплины и семестра
+            if (!await IsAssignmentExistsAsync())
+            {
+                MessageBox.Show("Данная дисциплина не назначена выбранной группе в указанном семестре.\nНевозможно выставить оценку.",
+                    "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+                return;
+            }
+
+            int recordBook = (int)dgvStudents.CurrentRow.Cells["record_book"].Value;
+            int disciplineId = (int)cmbDiscipline.SelectedValue;
+            int newGrade = (int)nudGrade.Value;
+            DateTime examDate = dtpExamDate.Value;
+
+            string checkSql = "SELECT progress_id FROM Progress WHERE record_book = @rb AND discipline_id = @did";
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    int? existingId = null;
+                    using (var cmd = new NpgsqlCommand(checkSql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@rb", recordBook);
+                        cmd.Parameters.AddWithValue("@did", disciplineId);
+                        var result = await cmd.ExecuteScalarAsync();
+                        if (result != null && result != DBNull.Value)
+                            existingId = Convert.ToInt32(result);
+                    }
+
+                    if (existingId.HasValue)
+                    {
+                        string updateSql = "UPDATE Progress SET grade = @g, exam_date = @ed WHERE progress_id = @pid";
+                        using (var cmd = new NpgsqlCommand(updateSql, conn))
+                        {
+                            cmd.Parameters.AddWithValue("@g", newGrade);
+                            cmd.Parameters.AddWithValue("@ed", examDate);
+                            cmd.Parameters.AddWithValue("@pid", existingId.Value);
+                            await cmd.ExecuteNonQueryAsync();
+                        }
+                    }
+                    else
+                    {
+                        string insertSql = "INSERT INTO Progress (record_book, discipline_id, grade, exam_date) VALUES (@rb, @did, @g, @ed)";
+                        using (var cmd = new NpgsqlCommand(insertSql, conn))
+                        {
+                            cmd.Parameters.AddWithValue("@rb", recordBook);
+                            cmd.Parameters.AddWithValue("@did", disciplineId);
+                            cmd.Parameters.AddWithValue("@g", newGrade);
+                            cmd.Parameters.AddWithValue("@ed", examDate);
+                            await cmd.ExecuteNonQueryAsync();
+                        }
+                    }
+                }
+                await LoadStudentsAndGradesAsync();
+                MessageBox.Show("Оценка сохранена.");
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+            finally
+            {
+                Cursor = Cursors.Default;
+            }
+        }
+
+        // Проверка существования назначения (Assignment) для группы, дисциплины и семестра
+        private async Task<bool> IsAssignmentExistsAsync()
+        {
+            if (cmbGroup.SelectedValue == null || cmbDiscipline.SelectedValue == null || cmbSemester.SelectedItem == null)
+                return false;
+
+            int groupId = (int)cmbGroup.SelectedValue;
+            int disciplineId = (int)cmbDiscipline.SelectedValue;
+            int semester = (int)cmbSemester.SelectedItem;
+
+            string sql = "SELECT COUNT(*) FROM Assignment WHERE group_id = @gid AND discipline_id = @did AND semester = @sem";
+            try
+            {
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@gid", groupId);
+                        cmd.Parameters.AddWithValue("@did", disciplineId);
+                        cmd.Parameters.AddWithValue("@sem", semester);
+                        long count = (long)await cmd.ExecuteScalarAsync();
+                        return count > 0;
+                    }
+                }
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
+        private async void cmbGroup_SelectedIndexChanged(object sender, EventArgs e)
+        {
+            await LoadStudentsAndGradesAsync();
+        }
+
+        private async void cmbDiscipline_SelectedIndexChanged(object sender, EventArgs e)
+        {
+            await LoadStudentsAndGradesAsync();
+        }
+
+        private async void cmbSemester_SelectedIndexChanged(object sender, EventArgs e)
+        {
+            await LoadStudentsAndGradesAsync();
+        }
+
+        private async void btnRefresh_Click(object sender, EventArgs e)
+        {
+            await LoadStudentsAndGradesAsync();
+        }
+
+        private void txtSearchStudent_TextChanged(object sender, EventArgs e)
+        {
+            ApplyFilter();
+        }
+
+        private void dgvStudents_SelectionChanged(object sender, EventArgs e)
+        {
+            if (dgvStudents.CurrentRow != null && dgvStudents.CurrentRow.Cells["grade"].Value != DBNull.Value)
+            {
+                int grade = Convert.ToInt32(dgvStudents.CurrentRow.Cells["grade"].Value);
+                nudGrade.Value = (grade >= 2 && grade <= 5) ? grade : 2;
+                if (dgvStudents.CurrentRow.Cells["exam_date"].Value != DBNull.Value)
+                    dtpExamDate.Value = Convert.ToDateTime(dgvStudents.CurrentRow.Cells["exam_date"].Value);
+            }
+            else
+            {
+                nudGrade.Value = 2;
+                dtpExamDate.Value = DateTime.Now;
+            }
+        }
+    }
+}

+ 120 - 0
GradeForm.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 152 - 0
LoginForm.Designer.cs

@@ -0,0 +1,152 @@
+namespace DeanOfficeApp
+{
+    partial class LoginForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+                components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            this.txtUsername = new Guna.UI2.WinForms.Guna2TextBox();
+            this.txtPassword = new Guna.UI2.WinForms.Guna2TextBox();
+            this.btnLogin = new Guna.UI2.WinForms.Guna2Button();
+            this.btnExit = new Guna.UI2.WinForms.Guna2Button();
+            this.chkShowPassword = new Guna.UI2.WinForms.Guna2CheckBox();
+            this.lblTitle = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.panelSide = new Guna.UI2.WinForms.Guna2Panel();
+            this.panelLogin = new Guna.UI2.WinForms.Guna2Panel();
+            this.SuspendLayout();
+            // 
+            // txtUsername
+            // 
+            this.txtUsername.Cursor = System.Windows.Forms.Cursors.IBeam;
+            this.txtUsername.DefaultText = "";
+            this.txtUsername.FillColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(249)))), ((int)(((byte)(250)))));
+            this.txtUsername.Font = new System.Drawing.Font("Segoe UI", 10F);
+            this.txtUsername.Location = new System.Drawing.Point(320, 180);
+            this.txtUsername.Name = "txtUsername";
+            this.txtUsername.PlaceholderText = "Логин";
+            this.txtUsername.SelectedText = "";
+            this.txtUsername.Size = new System.Drawing.Size(240, 42);
+            this.txtUsername.TabIndex = 5;
+            // 
+            // txtPassword
+            // 
+            this.txtPassword.Cursor = System.Windows.Forms.Cursors.IBeam;
+            this.txtPassword.DefaultText = "";
+            this.txtPassword.FillColor = System.Drawing.Color.FromArgb(((int)(((byte)(248)))), ((int)(((byte)(249)))), ((int)(((byte)(250)))));
+            this.txtPassword.Font = new System.Drawing.Font("Segoe UI", 10F);
+            this.txtPassword.Location = new System.Drawing.Point(320, 240);
+            this.txtPassword.Name = "txtPassword";
+            this.txtPassword.PlaceholderText = "Пароль";
+            this.txtPassword.SelectedText = "";
+            this.txtPassword.Size = new System.Drawing.Size(240, 42);
+            this.txtPassword.TabIndex = 4;
+            // 
+            // btnLogin
+            // 
+            this.btnLogin.BorderRadius = 12;
+            this.btnLogin.FillColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(167)))), ((int)(((byte)(69)))));
+            this.btnLogin.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
+            this.btnLogin.ForeColor = System.Drawing.Color.White;
+            this.btnLogin.Location = new System.Drawing.Point(320, 330);
+            this.btnLogin.Name = "btnLogin";
+            this.btnLogin.Size = new System.Drawing.Size(110, 40);
+            this.btnLogin.TabIndex = 3;
+            this.btnLogin.Text = "Войти";
+            this.btnLogin.Click += new System.EventHandler(this.btnLogin_Click);
+            // 
+            // btnExit
+            // 
+            this.btnExit.BorderRadius = 12;
+            this.btnExit.FillColor = System.Drawing.Color.FromArgb(((int)(((byte)(220)))), ((int)(((byte)(53)))), ((int)(((byte)(69)))));
+            this.btnExit.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Bold);
+            this.btnExit.ForeColor = System.Drawing.Color.White;
+            this.btnExit.Location = new System.Drawing.Point(450, 330);
+            this.btnExit.Name = "btnExit";
+            this.btnExit.Size = new System.Drawing.Size(110, 40);
+            this.btnExit.TabIndex = 2;
+            this.btnExit.Text = "Выход";
+            this.btnExit.Click += new System.EventHandler(this.btnExit_Click);
+            // 
+            // chkShowPassword
+            // 
+            this.chkShowPassword.AutoSize = true;
+            this.chkShowPassword.CheckedState.BorderRadius = 0;
+            this.chkShowPassword.CheckedState.BorderThickness = 0;
+            this.chkShowPassword.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.chkShowPassword.Location = new System.Drawing.Point(320, 295);
+            this.chkShowPassword.Name = "chkShowPassword";
+            this.chkShowPassword.Size = new System.Drawing.Size(119, 19);
+            this.chkShowPassword.TabIndex = 1;
+            this.chkShowPassword.Text = "Показать пароль";
+            this.chkShowPassword.UncheckedState.BorderRadius = 0;
+            this.chkShowPassword.UncheckedState.BorderThickness = 0;
+            this.chkShowPassword.CheckedChanged += new System.EventHandler(this.chkShowPassword_CheckedChanged);
+            // 
+            // lblTitle
+            // 
+            this.lblTitle.BackColor = System.Drawing.Color.Transparent;
+            this.lblTitle.Font = new System.Drawing.Font("Segoe UI", 24F, System.Drawing.FontStyle.Bold);
+            this.lblTitle.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(44)))), ((int)(((byte)(62)))), ((int)(((byte)(80)))));
+            this.lblTitle.Location = new System.Drawing.Point(320, 110);
+            this.lblTitle.Name = "lblTitle";
+            this.lblTitle.Size = new System.Drawing.Size(132, 47);
+            this.lblTitle.TabIndex = 0;
+            this.lblTitle.Text = "Деканат";
+            this.lblTitle.TextAlignment = System.Drawing.ContentAlignment.MiddleCenter;
+            // 
+            // panelSide
+            // 
+            this.panelSide.FillColor = System.Drawing.Color.FromArgb(((int)(((byte)(52)))), ((int)(((byte)(73)))), ((int)(((byte)(94)))));
+            this.panelSide.Location = new System.Drawing.Point(0, 0);
+            this.panelSide.Name = "panelSide";
+            this.panelSide.Size = new System.Drawing.Size(250, 450);
+            this.panelSide.TabIndex = 6;
+            // 
+            // panelLogin
+            // 
+            this.panelLogin.FillColor = System.Drawing.Color.White;
+            this.panelLogin.Location = new System.Drawing.Point(250, 0);
+            this.panelLogin.Name = "panelLogin";
+            this.panelLogin.Size = new System.Drawing.Size(470, 450);
+            this.panelLogin.TabIndex = 7;
+            this.panelLogin.Paint += new System.Windows.Forms.PaintEventHandler(this.panelLogin_Paint);
+            // 
+            // LoginForm
+            // 
+            this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(240)))), ((int)(((byte)(242)))), ((int)(((byte)(245)))));
+            this.ClientSize = new System.Drawing.Size(720, 450);
+            this.Controls.Add(this.lblTitle);
+            this.Controls.Add(this.chkShowPassword);
+            this.Controls.Add(this.btnExit);
+            this.Controls.Add(this.btnLogin);
+            this.Controls.Add(this.txtPassword);
+            this.Controls.Add(this.txtUsername);
+            this.Controls.Add(this.panelSide);
+            this.Controls.Add(this.panelLogin);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
+            this.Name = "LoginForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+            this.Text = "Авторизация";
+            this.ResumeLayout(false);
+            this.PerformLayout();
+
+        }
+
+        private Guna.UI2.WinForms.Guna2TextBox txtUsername;
+        private Guna.UI2.WinForms.Guna2TextBox txtPassword;
+        private Guna.UI2.WinForms.Guna2Button btnLogin;
+        private Guna.UI2.WinForms.Guna2Button btnExit;
+        private Guna.UI2.WinForms.Guna2CheckBox chkShowPassword;
+        private Guna.UI2.WinForms.Guna2HtmlLabel lblTitle;
+        private Guna.UI2.WinForms.Guna2Panel panelSide;
+        private Guna.UI2.WinForms.Guna2Panel panelLogin;
+    }
+}

+ 88 - 0
LoginForm.cs

@@ -0,0 +1,88 @@
+using Npgsql;
+using System;
+using System.Security.Cryptography;
+using System.Text;
+using System.Windows.Forms;
+
+namespace DeanOfficeApp
+{
+    public partial class LoginForm : Form
+    {
+        private readonly string _connString = "Host=245e1-rw.db.pub.dbaas.postgrespro.ru;Port=5432;Database=dbpractice;Username=mardasov_mv;Password=A4%2x$$2Ynr";
+
+        public string UserRole { get; private set; }
+        public string Username { get; private set; }
+        public string FullName { get; private set; }
+
+        public LoginForm()
+        {
+            InitializeComponent();
+            txtPassword.PasswordChar = '*';
+        }
+
+        private async void btnLogin_Click(object sender, EventArgs e)
+        {
+            string username = txtUsername.Text.Trim();
+            string password = txtPassword.Text;
+            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
+            {
+                MessageBox.Show("Введите логин и пароль!", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+                return;
+            }
+
+            string hash = ComputeMd5(password);
+
+            try
+            {
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    string sql = "SELECT role, full_name FROM appuser WHERE username = @user AND password_hash = @hash";
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@user", username);
+                        cmd.Parameters.AddWithValue("@hash", hash);
+                        using (var reader = await cmd.ExecuteReaderAsync())
+                        {
+                            if (await reader.ReadAsync())
+                            {
+                                UserRole = reader.GetString(0);
+                                FullName = reader.GetString(1);
+                                Username = username;
+                                DialogResult = DialogResult.OK;
+                                Close();
+                            }
+                            else
+                            {
+                                MessageBox.Show("Неверный логин или пароль!", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+                            }
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка подключения: {ex.Message}", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+            }
+        }
+
+        private string ComputeMd5(string input)
+        {
+            using (MD5 md5 = MD5.Create())
+            {
+                byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(input));
+                StringBuilder sb = new StringBuilder();
+                foreach (byte b in hash) sb.Append(b.ToString("x2"));
+                return sb.ToString();
+            }
+        }
+
+        private void btnExit_Click(object sender, EventArgs e) => Application.Exit();
+        private void chkShowPassword_CheckedChanged(object sender, EventArgs e) => txtPassword.PasswordChar = chkShowPassword.Checked ? '\0' : '*';
+
+        private void panelLogin_Paint(object sender, PaintEventArgs e)
+        {
+
+        }
+    }
+}

+ 120 - 0
LoginForm.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 417 - 0
MainForm.Designer.cs

@@ -0,0 +1,417 @@
+namespace DeanOfficeApp
+{
+    partial class MainForm
+    {
+        private System.ComponentModel.IContainer components = null;
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && (components != null))
+                components.Dispose();
+            base.Dispose(disposing);
+        }
+
+        private void InitializeComponent()
+        {
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle4 = new System.Windows.Forms.DataGridViewCellStyle();
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle5 = new System.Windows.Forms.DataGridViewCellStyle();
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle6 = new System.Windows.Forms.DataGridViewCellStyle();
+            this.panelTop = new Guna.UI2.WinForms.Guna2Panel();
+            this.controlBox = new Guna.UI2.WinForms.Guna2ControlBox();
+            this.panelLeft = new Guna.UI2.WinForms.Guna2Panel();
+            this.cmbTeachers = new Guna.UI2.WinForms.Guna2ComboBox();
+            this.btnStudents = new Guna.UI2.WinForms.Guna2Button();
+            this.btnUsers = new Guna.UI2.WinForms.Guna2Button();
+            this.btnGrades = new Guna.UI2.WinForms.Guna2Button();
+            this.btnAssignments = new Guna.UI2.WinForms.Guna2Button();
+            this.cmbGroups = new Guna.UI2.WinForms.Guna2ComboBox();
+            this.rbAsc = new Guna.UI2.WinForms.Guna2RadioButton();
+            this.rbDesc = new Guna.UI2.WinForms.Guna2RadioButton();
+            this.btnLoad = new Guna.UI2.WinForms.Guna2Button();
+            this.gbDisciplines = new Guna.UI2.WinForms.Guna2GroupBox();
+            this.txtDiscName = new Guna.UI2.WinForms.Guna2TextBox();
+            this.numHours = new Guna.UI2.WinForms.Guna2NumericUpDown();
+            this.btnAdd = new Guna.UI2.WinForms.Guna2Button();
+            this.btnUpdate = new Guna.UI2.WinForms.Guna2Button();
+            this.btnDelete = new Guna.UI2.WinForms.Guna2Button();
+            this.panelRight = new Guna.UI2.WinForms.Guna2Panel();
+            this.dgvDisciplines = new Guna.UI2.WinForms.Guna2DataGridView();
+            this.dgvSchedule = new Guna.UI2.WinForms.Guna2DataGridView();
+            this.btnExportCsv = new Guna.UI2.WinForms.Guna2Button();
+            this.btnExportHtml = new Guna.UI2.WinForms.Guna2Button();
+            this.panelTop.SuspendLayout();
+            this.panelLeft.SuspendLayout();
+            this.gbDisciplines.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.numHours)).BeginInit();
+            this.panelRight.SuspendLayout();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvDisciplines)).BeginInit();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvSchedule)).BeginInit();
+            this.SuspendLayout();
+
+            // panelTop
+            this.panelTop.Controls.Add(this.controlBox);
+            this.panelTop.Dock = System.Windows.Forms.DockStyle.Top;
+            this.panelTop.FillColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            this.panelTop.Location = new System.Drawing.Point(0, 0);
+            this.panelTop.Name = "panelTop";
+            this.panelTop.Size = new System.Drawing.Size(1000, 40);
+            this.panelTop.TabIndex = 0;
+
+            // controlBox
+            this.controlBox.Anchor = (System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right);
+            this.controlBox.FillColor = System.Drawing.Color.FromArgb(231, 76, 60);
+            this.controlBox.IconColor = System.Drawing.Color.White;
+            this.controlBox.Location = new System.Drawing.Point(960, 6);
+            this.controlBox.Name = "controlBox";
+            this.controlBox.Size = new System.Drawing.Size(30, 28);
+            this.controlBox.TabIndex = 0;
+            this.controlBox.Click += new System.EventHandler(this.controlBox_Click);
+
+            // panelLeft
+            this.panelLeft.Controls.Add(this.cmbTeachers);
+            this.panelLeft.Controls.Add(this.btnStudents);
+            this.panelLeft.Controls.Add(this.btnUsers);
+            this.panelLeft.Controls.Add(this.btnGrades);
+            this.panelLeft.Controls.Add(this.btnAssignments);
+            this.panelLeft.Controls.Add(this.cmbGroups);
+            this.panelLeft.Controls.Add(this.rbAsc);
+            this.panelLeft.Controls.Add(this.rbDesc);
+            this.panelLeft.Controls.Add(this.btnLoad);
+            this.panelLeft.Controls.Add(this.gbDisciplines);
+            this.panelLeft.Dock = System.Windows.Forms.DockStyle.Left;
+            this.panelLeft.FillColor = System.Drawing.Color.FromArgb(248, 249, 250);
+            this.panelLeft.Location = new System.Drawing.Point(0, 40);
+            this.panelLeft.Name = "panelLeft";
+            this.panelLeft.Size = new System.Drawing.Size(260, 760);
+            this.panelLeft.TabIndex = 1;
+
+            // cmbTeachers
+            this.cmbTeachers.BackColor = System.Drawing.Color.Transparent;
+            this.cmbTeachers.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
+            this.cmbTeachers.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbTeachers.FocusedColor = System.Drawing.Color.FromArgb(94, 148, 255);
+            this.cmbTeachers.FocusedState.BorderColor = System.Drawing.Color.FromArgb(94, 148, 255);
+            this.cmbTeachers.Font = new System.Drawing.Font("Segoe UI", 10F);
+            this.cmbTeachers.ForeColor = System.Drawing.Color.FromArgb(68, 88, 112);
+            this.cmbTeachers.ItemHeight = 30;
+            this.cmbTeachers.Location = new System.Drawing.Point(20, 6);
+            this.cmbTeachers.Name = "cmbTeachers";
+            this.cmbTeachers.Size = new System.Drawing.Size(220, 36);
+            this.cmbTeachers.TabIndex = 5;
+            this.cmbTeachers.SelectedIndexChanged += new System.EventHandler(this.cmbTeachers_SelectedIndexChanged);
+
+            // btnStudents
+            this.btnStudents.BorderRadius = 10;
+            this.btnStudents.FillColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            this.btnStudents.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnStudents.ForeColor = System.Drawing.Color.White;
+            this.btnStudents.Location = new System.Drawing.Point(20, 470);
+            this.btnStudents.Name = "btnStudents";
+            this.btnStudents.Size = new System.Drawing.Size(220, 40);
+            this.btnStudents.TabIndex = 6;
+            this.btnStudents.Text = "👥 Студенты";
+            this.btnStudents.Click += new System.EventHandler(this.btnStudents_Click);
+
+            // btnUsers
+            this.btnUsers.BorderRadius = 10;
+            this.btnUsers.FillColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            this.btnUsers.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnUsers.ForeColor = System.Drawing.Color.White;
+            this.btnUsers.Location = new System.Drawing.Point(20, 525);
+            this.btnUsers.Name = "btnUsers";
+            this.btnUsers.Size = new System.Drawing.Size(220, 40);
+            this.btnUsers.TabIndex = 7;
+            this.btnUsers.Text = "👥 Управление пользователями";
+            this.btnUsers.Click += new System.EventHandler(this.btnUsers_Click);
+
+            // btnGrades (новая)
+            this.btnGrades.BorderRadius = 10;
+            this.btnGrades.FillColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            this.btnGrades.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnGrades.ForeColor = System.Drawing.Color.White;
+            this.btnGrades.Location = new System.Drawing.Point(20, 580);
+            this.btnGrades.Name = "btnGrades";
+            this.btnGrades.Size = new System.Drawing.Size(220, 40);
+            this.btnGrades.TabIndex = 8;
+            this.btnGrades.Text = "📝 Успеваемость";
+            this.btnGrades.Click += new System.EventHandler(this.btnGrades_Click);
+
+            // btnAssignments (новая)
+            this.btnAssignments.BorderRadius = 10;
+            this.btnAssignments.FillColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            this.btnAssignments.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnAssignments.ForeColor = System.Drawing.Color.White;
+            this.btnAssignments.Location = new System.Drawing.Point(20, 635);
+            this.btnAssignments.Name = "btnAssignments";
+            this.btnAssignments.Size = new System.Drawing.Size(220, 40);
+            this.btnAssignments.TabIndex = 9;
+            this.btnAssignments.Text = "📅 Назначения (расписание)";
+            this.btnAssignments.Click += new System.EventHandler(this.btnAssignments_Click);
+
+            // cmbGroups
+            this.cmbGroups.BackColor = System.Drawing.Color.Transparent;
+            this.cmbGroups.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
+            this.cmbGroups.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbGroups.FocusedColor = System.Drawing.Color.Empty;
+            this.cmbGroups.Font = new System.Drawing.Font("Segoe UI", 10F);
+            this.cmbGroups.ForeColor = System.Drawing.Color.FromArgb(68, 88, 112);
+            this.cmbGroups.ItemHeight = 30;
+            this.cmbGroups.Location = new System.Drawing.Point(20, 48);
+            this.cmbGroups.Name = "cmbGroups";
+            this.cmbGroups.Size = new System.Drawing.Size(220, 36);
+            this.cmbGroups.TabIndex = 0;
+
+
+            // rbAsc
+            this.rbAsc.AutoSize = true;
+            this.rbAsc.CheckedState.BorderThickness = 0;
+            this.rbAsc.Location = new System.Drawing.Point(20, 90);
+            this.rbAsc.Name = "rbAsc";
+            this.rbAsc.Size = new System.Drawing.Size(109, 17);
+            this.rbAsc.TabIndex = 1;
+            this.rbAsc.Text = "По возрастанию";
+            this.rbAsc.UncheckedState.BorderThickness = 0;
+
+            // rbDesc
+            this.rbDesc.AutoSize = true;
+            this.rbDesc.CheckedState.BorderThickness = 0;
+            this.rbDesc.Location = new System.Drawing.Point(20, 120);
+            this.rbDesc.Name = "rbDesc";
+            this.rbDesc.Size = new System.Drawing.Size(93, 17);
+            this.rbDesc.TabIndex = 2;
+            this.rbDesc.Text = "По убыванию";
+            this.rbDesc.UncheckedState.BorderThickness = 0;
+
+            // btnLoad
+            this.btnLoad.BorderRadius = 10;
+            this.btnLoad.FillColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            this.btnLoad.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnLoad.ForeColor = System.Drawing.Color.White;
+            this.btnLoad.Location = new System.Drawing.Point(20, 160);
+            this.btnLoad.Name = "btnLoad";
+            this.btnLoad.Size = new System.Drawing.Size(220, 40);
+            this.btnLoad.TabIndex = 3;
+            this.btnLoad.Text = "Загрузить расписание";
+            this.btnLoad.Click += new System.EventHandler(this.btnLoad_Click);
+
+            // gbDisciplines
+            this.gbDisciplines.Controls.Add(this.txtDiscName);
+            this.gbDisciplines.Controls.Add(this.numHours);
+            this.gbDisciplines.Controls.Add(this.btnAdd);
+            this.gbDisciplines.Controls.Add(this.btnUpdate);
+            this.gbDisciplines.Controls.Add(this.btnDelete);
+            this.gbDisciplines.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.gbDisciplines.ForeColor = System.Drawing.Color.Black;
+            this.gbDisciplines.Location = new System.Drawing.Point(20, 230);
+            this.gbDisciplines.Name = "gbDisciplines";
+            this.gbDisciplines.Size = new System.Drawing.Size(220, 280);
+            this.gbDisciplines.TabIndex = 4;
+            this.gbDisciplines.TabStop = false;
+            this.gbDisciplines.Text = "Управление дисциплинами";
+
+            // txtDiscName
+            this.txtDiscName.Cursor = System.Windows.Forms.Cursors.IBeam;
+            this.txtDiscName.DefaultText = "";
+            this.txtDiscName.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.txtDiscName.Location = new System.Drawing.Point(15, 40);
+            this.txtDiscName.Name = "txtDiscName";
+            this.txtDiscName.PlaceholderText = "Название";
+            this.txtDiscName.SelectedText = "";
+            this.txtDiscName.Size = new System.Drawing.Size(190, 36);
+            this.txtDiscName.TabIndex = 0;
+
+            // numHours
+            this.numHours.BackColor = System.Drawing.Color.Transparent;
+            this.numHours.Cursor = System.Windows.Forms.Cursors.IBeam;
+            this.numHours.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.numHours.Location = new System.Drawing.Point(15, 90);
+            this.numHours.Name = "numHours";
+            this.numHours.Size = new System.Drawing.Size(100, 36);
+            this.numHours.TabIndex = 1;
+
+            // btnAdd
+            this.btnAdd.BorderRadius = 8;
+            this.btnAdd.FillColor = System.Drawing.Color.FromArgb(40, 167, 69);
+            this.btnAdd.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnAdd.ForeColor = System.Drawing.Color.White;
+            this.btnAdd.Location = new System.Drawing.Point(15, 145);
+            this.btnAdd.Name = "btnAdd";
+            this.btnAdd.Size = new System.Drawing.Size(85, 35);
+            this.btnAdd.TabIndex = 2;
+            this.btnAdd.Text = "Добавить";
+            this.btnAdd.Click += new System.EventHandler(this.btnAdd_Click);
+
+            // btnUpdate
+            this.btnUpdate.BorderRadius = 8;
+            this.btnUpdate.FillColor = System.Drawing.Color.FromArgb(255, 193, 7);
+            this.btnUpdate.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnUpdate.ForeColor = System.Drawing.Color.Black;
+            this.btnUpdate.Location = new System.Drawing.Point(110, 145);
+            this.btnUpdate.Name = "btnUpdate";
+            this.btnUpdate.Size = new System.Drawing.Size(85, 35);
+            this.btnUpdate.TabIndex = 3;
+            this.btnUpdate.Text = "Обновить";
+            this.btnUpdate.Click += new System.EventHandler(this.btnUpdate_Click);
+
+            // btnDelete
+            this.btnDelete.BorderRadius = 8;
+            this.btnDelete.FillColor = System.Drawing.Color.FromArgb(220, 53, 69);
+            this.btnDelete.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnDelete.ForeColor = System.Drawing.Color.White;
+            this.btnDelete.Location = new System.Drawing.Point(15, 195);
+            this.btnDelete.Name = "btnDelete";
+            this.btnDelete.Size = new System.Drawing.Size(180, 35);
+            this.btnDelete.TabIndex = 4;
+            this.btnDelete.Text = "Удалить выбранную";
+            this.btnDelete.Click += new System.EventHandler(this.btnDelete_Click);
+
+            // panelRight
+            this.panelRight.Controls.Add(this.dgvDisciplines);
+            this.panelRight.Controls.Add(this.dgvSchedule);
+            this.panelRight.Controls.Add(this.btnExportCsv);
+            this.panelRight.Controls.Add(this.btnExportHtml);
+            this.panelRight.Dock = System.Windows.Forms.DockStyle.Fill;
+            this.panelRight.FillColor = System.Drawing.Color.FromArgb(240, 242, 245);
+            this.panelRight.Location = new System.Drawing.Point(260, 40);
+            this.panelRight.Name = "panelRight";
+            this.panelRight.Size = new System.Drawing.Size(740, 760);
+            this.panelRight.TabIndex = 2;
+            this.panelRight.Paint += new System.Windows.Forms.PaintEventHandler(this.panelRight_Paint);
+
+            // dgvDisciplines
+            this.dgvDisciplines.AllowUserToAddRows = false;
+            this.dgvDisciplines.AllowUserToDeleteRows = false;
+            dataGridViewCellStyle1.BackColor = System.Drawing.Color.White;
+            this.dgvDisciplines.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1;
+            dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle2.BackColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            dataGridViewCellStyle2.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
+            dataGridViewCellStyle2.ForeColor = System.Drawing.Color.White;
+            dataGridViewCellStyle2.SelectionBackColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
+            this.dgvDisciplines.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2;
+            this.dgvDisciplines.ColumnHeadersHeight = 40;
+            dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle3.BackColor = System.Drawing.Color.White;
+            dataGridViewCellStyle3.Font = new System.Drawing.Font("Segoe UI", 9F);
+            dataGridViewCellStyle3.ForeColor = System.Drawing.Color.FromArgb(64, 64, 64);
+            dataGridViewCellStyle3.SelectionBackColor = System.Drawing.Color.FromArgb(229, 241, 251);
+            dataGridViewCellStyle3.SelectionForeColor = System.Drawing.Color.Black;
+            dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
+            this.dgvDisciplines.DefaultCellStyle = dataGridViewCellStyle3;
+            this.dgvDisciplines.GridColor = System.Drawing.Color.FromArgb(230, 230, 230);
+            this.dgvDisciplines.Location = new System.Drawing.Point(20, 420);
+            this.dgvDisciplines.Name = "dgvDisciplines";
+            this.dgvDisciplines.ReadOnly = true;
+            this.dgvDisciplines.RowHeadersVisible = false;
+            this.dgvDisciplines.RowTemplate.Height = 30;
+            this.dgvDisciplines.Size = new System.Drawing.Size(700, 220);
+            this.dgvDisciplines.TabIndex = 1;
+            this.dgvDisciplines.SelectionChanged += new System.EventHandler(this.dgvDisciplines_SelectionChanged);
+
+            // dgvSchedule
+            this.dgvSchedule.AllowUserToAddRows = false;
+            this.dgvSchedule.AllowUserToDeleteRows = false;
+            dataGridViewCellStyle4.BackColor = System.Drawing.Color.White;
+            this.dgvSchedule.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle4;
+            dataGridViewCellStyle5.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle5.BackColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            dataGridViewCellStyle5.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
+            dataGridViewCellStyle5.ForeColor = System.Drawing.Color.White;
+            dataGridViewCellStyle5.SelectionBackColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            dataGridViewCellStyle5.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
+            this.dgvSchedule.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle5;
+            this.dgvSchedule.ColumnHeadersHeight = 40;
+            dataGridViewCellStyle6.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle6.BackColor = System.Drawing.Color.White;
+            dataGridViewCellStyle6.Font = new System.Drawing.Font("Segoe UI", 9F);
+            dataGridViewCellStyle6.ForeColor = System.Drawing.Color.FromArgb(64, 64, 64);
+            dataGridViewCellStyle6.SelectionBackColor = System.Drawing.Color.FromArgb(229, 241, 251);
+            dataGridViewCellStyle6.SelectionForeColor = System.Drawing.Color.Black;
+            dataGridViewCellStyle6.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
+            this.dgvSchedule.DefaultCellStyle = dataGridViewCellStyle6;
+            this.dgvSchedule.GridColor = System.Drawing.Color.FromArgb(230, 230, 230);
+            this.dgvSchedule.Location = new System.Drawing.Point(20, 20);
+            this.dgvSchedule.Name = "dgvSchedule";
+            this.dgvSchedule.ReadOnly = true;
+            this.dgvSchedule.RowHeadersVisible = false;
+            this.dgvSchedule.RowTemplate.Height = 30;
+            this.dgvSchedule.Size = new System.Drawing.Size(700, 300);
+            this.dgvSchedule.TabIndex = 0;
+
+            // btnExportCsv
+            this.btnExportCsv.BorderRadius = 8;
+            this.btnExportCsv.FillColor = System.Drawing.Color.FromArgb(40, 167, 69);
+            this.btnExportCsv.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnExportCsv.ForeColor = System.Drawing.Color.White;
+            this.btnExportCsv.Location = new System.Drawing.Point(20, 340);
+            this.btnExportCsv.Name = "btnExportCsv";
+            this.btnExportCsv.Size = new System.Drawing.Size(130, 40);
+            this.btnExportCsv.TabIndex = 2;
+            this.btnExportCsv.Text = "📎 Экспорт CSV";
+            this.btnExportCsv.Click += new System.EventHandler(this.btnExportCsv_Click);
+
+            // btnExportHtml
+            this.btnExportHtml.BorderRadius = 8;
+            this.btnExportHtml.FillColor = System.Drawing.Color.FromArgb(0, 123, 255);
+            this.btnExportHtml.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnExportHtml.ForeColor = System.Drawing.Color.White;
+            this.btnExportHtml.Location = new System.Drawing.Point(170, 340);
+            this.btnExportHtml.Name = "btnExportHtml";
+            this.btnExportHtml.Size = new System.Drawing.Size(130, 40);
+            this.btnExportHtml.TabIndex = 3;
+            this.btnExportHtml.Text = "🌐 Экспорт HTML";
+            this.btnExportHtml.Click += new System.EventHandler(this.btnExportHtml_Click);
+
+            // MainForm
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.BackColor = System.Drawing.Color.FromArgb(240, 242, 245);
+            this.ClientSize = new System.Drawing.Size(1000, 800);
+            this.Controls.Add(this.panelRight);
+            this.Controls.Add(this.panelLeft);
+            this.Controls.Add(this.panelTop);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
+            this.Name = "MainForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+            this.Text = "Деканат";
+            this.panelTop.ResumeLayout(false);
+            this.panelLeft.ResumeLayout(false);
+            this.panelLeft.PerformLayout();
+            this.gbDisciplines.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.numHours)).EndInit();
+            this.panelRight.ResumeLayout(false);
+            ((System.ComponentModel.ISupportInitialize)(this.dgvDisciplines)).EndInit();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvSchedule)).EndInit();
+            this.ResumeLayout(false);
+        }
+
+        // Объявления компонентов
+        private Guna.UI2.WinForms.Guna2Panel panelTop;
+        private Guna.UI2.WinForms.Guna2ControlBox controlBox;
+        private Guna.UI2.WinForms.Guna2Panel panelLeft;
+        private Guna.UI2.WinForms.Guna2ComboBox cmbGroups;
+        private Guna.UI2.WinForms.Guna2RadioButton rbAsc;
+        private Guna.UI2.WinForms.Guna2RadioButton rbDesc;
+        private Guna.UI2.WinForms.Guna2Button btnLoad;
+        private Guna.UI2.WinForms.Guna2GroupBox gbDisciplines;
+        private Guna.UI2.WinForms.Guna2TextBox txtDiscName;
+        private Guna.UI2.WinForms.Guna2NumericUpDown numHours;
+        private Guna.UI2.WinForms.Guna2Button btnAdd;
+        private Guna.UI2.WinForms.Guna2Button btnUpdate;
+        private Guna.UI2.WinForms.Guna2Button btnDelete;
+        private Guna.UI2.WinForms.Guna2Panel panelRight;
+        private Guna.UI2.WinForms.Guna2DataGridView dgvSchedule;
+        private Guna.UI2.WinForms.Guna2DataGridView dgvDisciplines;
+        private Guna.UI2.WinForms.Guna2Button btnExportCsv;
+        private Guna.UI2.WinForms.Guna2Button btnExportHtml;
+        private Guna.UI2.WinForms.Guna2ComboBox cmbTeachers;
+        private Guna.UI2.WinForms.Guna2Button btnStudents;
+        private Guna.UI2.WinForms.Guna2Button btnUsers;
+        private Guna.UI2.WinForms.Guna2Button btnGrades;
+        private Guna.UI2.WinForms.Guna2Button btnAssignments;
+    }
+}

+ 501 - 0
MainForm.cs

@@ -0,0 +1,501 @@
+using CsvHelper;
+using Npgsql;
+using System;
+using System.Data;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace DeanOfficeApp
+{
+    public partial class MainForm : Form
+    {
+        private readonly string _connString = "Host=245e1-rw.db.pub.dbaas.postgrespro.ru;Port=5432;Database=dbpractice;Username=mardasov_mv;Password=A4%2x$$2Ynr";
+        private readonly string _userRole;
+
+        public MainForm(string userRole, string username, string fullName)
+        {
+            InitializeComponent();
+            _userRole = userRole;
+            this.Text = $"Деканат - {fullName} ({userRole})";
+            bool canEdit = (userRole == "admin" || userRole == "manager");
+            gbDisciplines.Visible = canEdit;
+            btnGrades.Visible = canEdit;
+            btnAssignments.Visible = canEdit;
+
+            numHours.Minimum = 1;
+            numHours.Maximum = 1000;
+            numHours.Value = numHours.Minimum;
+
+            // Подписываемся на события (если в дизайнере не подписаны)
+            cmbGroups.SelectedIndexChanged += cmbGroups_SelectedIndexChanged;
+            rbAsc.CheckedChanged += rbSort_CheckedChanged;
+            rbDesc.CheckedChanged += rbSort_CheckedChanged;
+
+            // Асинхронная загрузка начальных данных
+            _ = LoadInitialDataAsync();
+        }
+
+        private async Task LoadInitialDataAsync()
+        {
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                await LoadGroupsAsync();
+                if (_userRole == "admin" || _userRole == "manager")
+                {
+                    await LoadDisciplinesAsync();
+                    await LoadTeachersAsync();
+                }
+            }
+            finally
+            {
+                Cursor = Cursors.Default;
+            }
+        }
+
+        private async Task LoadGroupsAsync()
+        {
+            try
+            {
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    var cmd = new NpgsqlCommand("SELECT group_id, name FROM \"Group\" ORDER BY name", conn);
+                    var dt = new DataTable();
+                    dt.Load(await cmd.ExecuteReaderAsync());
+                    cmbGroups.DisplayMember = "name";
+                    cmbGroups.ValueMember = "group_id";
+                    cmbGroups.DataSource = dt;
+                    if (cmbGroups.Items.Count > 0)
+                        cmbGroups.SelectedIndex = 0; // автоматически выбрать первую группу
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки групп: {ex.Message}");
+            }
+        }
+
+        private async Task LoadTeachersAsync()
+        {
+            try
+            {
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    var cmd = new NpgsqlCommand("SELECT teacher_id, full_name FROM Teacher ORDER BY full_name", conn);
+                    var dt = new DataTable();
+                    dt.Load(await cmd.ExecuteReaderAsync());
+                    cmbTeachers.DisplayMember = "full_name";
+                    cmbTeachers.ValueMember = "teacher_id";
+                    cmbTeachers.DataSource = dt;
+                    if (cmbTeachers.Items.Count > 0)
+                        cmbTeachers.SelectedIndex = 0;
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки преподавателей: {ex.Message}");
+            }
+        }
+
+        private async Task LoadDisciplinesAsync()
+        {
+            try
+            {
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    var cmd = new NpgsqlCommand("SELECT discipline_id, name, hours FROM Discipline ORDER BY name", conn);
+                    var dt = new DataTable();
+                    dt.Load(await cmd.ExecuteReaderAsync());
+                    dgvDisciplines.DataSource = dt;
+                    if (dgvDisciplines.Columns.Contains("discipline_id"))
+                        dgvDisciplines.Columns["discipline_id"].Visible = false;
+                }
+                dgvDisciplines.ClearSelection();
+                txtDiscName.Clear();
+                numHours.Value = numHours.Minimum;
+                await RefreshScheduleAsync();
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки дисциплин: {ex.Message}");
+            }
+        }
+
+        private async Task RefreshScheduleAsync()
+        {
+            if (cmbGroups.SelectedValue != null)
+                await LoadScheduleAsync((int)cmbGroups.SelectedValue);
+        }
+
+        private async Task LoadScheduleAsync(int groupId)
+        {
+            // Безопасная сортировка: проверяем, что выбранное значение допустимо
+            string sortDirection = rbAsc.Checked ? "ASC" : "DESC";
+
+            // Используем параметризованный запрос, но ORDER BY нельзя параметризовать,
+            // поэтому проверяем значение перед вставкой.
+            if (sortDirection != "ASC" && sortDirection != "DESC")
+                sortDirection = "ASC";
+
+            string query = $@"
+                SELECT d.name AS Дисциплина, d.hours AS Часы, t.full_name AS Преподаватель
+                FROM Assignment a
+                JOIN Discipline d ON a.discipline_id = d.discipline_id
+                JOIN Teacher t ON a.teacher_id = t.teacher_id
+                WHERE a.group_id = @gid
+                ORDER BY d.hours {sortDirection}";
+
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    using (var cmd = new NpgsqlCommand(query, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@gid", groupId);
+                        var dt = new DataTable();
+                        dt.Load(await cmd.ExecuteReaderAsync());
+                        dgvSchedule.DataSource = dt;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки расписания: {ex.Message}");
+            }
+            finally
+            {
+                Cursor = Cursors.Default;
+            }
+        }
+
+        // Обработчик выбора группы
+        private async void cmbGroups_SelectedIndexChanged(object sender, EventArgs e)
+        {
+            await RefreshScheduleAsync();
+        }
+
+        // Обработчик изменения сортировки
+        private async void rbSort_CheckedChanged(object sender, EventArgs e)
+        {
+            await RefreshScheduleAsync();
+        }
+
+        // Кнопка "Загрузить расписание" (можно оставить для ручного обновления)
+        private async void btnLoad_Click(object sender, EventArgs e)
+        {
+            await RefreshScheduleAsync();
+        }
+
+        private async void btnAdd_Click(object sender, EventArgs e)
+        {
+            if (string.IsNullOrWhiteSpace(txtDiscName.Text))
+            {
+                MessageBox.Show("Введите название дисциплины.");
+                return;
+            }
+            if (numHours.Value < numHours.Minimum)
+            {
+                MessageBox.Show($"Количество часов должно быть не менее {numHours.Minimum}.");
+                return;
+            }
+            if (cmbGroups.SelectedValue == null)
+            {
+                MessageBox.Show("Выберите группу, к которой будет привязана дисциплина.");
+                return;
+            }
+            if (cmbTeachers.SelectedValue == null)
+            {
+                MessageBox.Show("Выберите преподавателя.");
+                return;
+            }
+
+            int groupId = (int)cmbGroups.SelectedValue;
+            int teacherId = (int)cmbTeachers.SelectedValue;
+            string discName = txtDiscName.Text.Trim();
+            int hours = (int)numHours.Value;
+
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+
+                    var cmdInsert = new NpgsqlCommand(
+                        "INSERT INTO Discipline (name, hours) VALUES (@n, @h) RETURNING discipline_id", conn);
+                    cmdInsert.Parameters.AddWithValue("@n", discName);
+                    cmdInsert.Parameters.AddWithValue("@h", hours);
+                    int newDisciplineId = (int)await cmdInsert.ExecuteScalarAsync();
+
+                    var cmdAssign = new NpgsqlCommand(
+                        "INSERT INTO Assignment (group_id, discipline_id, teacher_id, semester) VALUES (@g, @d, @t, 1)", conn);
+                    cmdAssign.Parameters.AddWithValue("@g", groupId);
+                    cmdAssign.Parameters.AddWithValue("@d", newDisciplineId);
+                    cmdAssign.Parameters.AddWithValue("@t", teacherId);
+                    await cmdAssign.ExecuteNonQueryAsync();
+                }
+
+                await LoadDisciplinesAsync();
+                txtDiscName.Clear();
+                numHours.Value = numHours.Minimum;
+                MessageBox.Show("Дисциплина добавлена и привязана к группе и преподавателю.");
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+            finally
+            {
+                Cursor = Cursors.Default;
+            }
+        }
+
+        private async void btnUpdate_Click(object sender, EventArgs e)
+        {
+            if (dgvDisciplines.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите дисциплину для обновления.");
+                return;
+            }
+            int id = (int)dgvDisciplines.CurrentRow.Cells["discipline_id"].Value;
+            if (string.IsNullOrWhiteSpace(txtDiscName.Text))
+            {
+                MessageBox.Show("Введите название.");
+                return;
+            }
+            if (numHours.Value < numHours.Minimum)
+            {
+                MessageBox.Show($"Количество часов должно быть не менее {numHours.Minimum}.");
+                return;
+            }
+
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    var cmd = new NpgsqlCommand("UPDATE Discipline SET name=@n, hours=@h WHERE discipline_id=@id", conn);
+                    cmd.Parameters.AddWithValue("@n", txtDiscName.Text.Trim());
+                    cmd.Parameters.AddWithValue("@h", (int)numHours.Value);
+                    cmd.Parameters.AddWithValue("@id", id);
+                    await cmd.ExecuteNonQueryAsync();
+                }
+                await LoadDisciplinesAsync();
+                MessageBox.Show("Дисциплина обновлена.");
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+            finally
+            {
+                Cursor = Cursors.Default;
+            }
+        }
+
+        private async void btnDelete_Click(object sender, EventArgs e)
+        {
+            if (dgvDisciplines.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите дисциплину для удаления.");
+                return;
+            }
+            int id = (int)dgvDisciplines.CurrentRow.Cells["discipline_id"].Value;
+            if (MessageBox.Show("Удалить дисциплину? Это также удалит все её связи (Assignment).",
+                "Подтверждение", MessageBoxButtons.YesNo) == DialogResult.Yes)
+            {
+                try
+                {
+                    Cursor = Cursors.WaitCursor;
+                    using (var conn = new NpgsqlConnection(_connString))
+                    {
+                        await conn.OpenAsync();
+                        var delAssign = new NpgsqlCommand("DELETE FROM Assignment WHERE discipline_id = @id", conn);
+                        delAssign.Parameters.AddWithValue("@id", id);
+                        await delAssign.ExecuteNonQueryAsync();
+
+                        var delDisc = new NpgsqlCommand("DELETE FROM Discipline WHERE discipline_id = @id", conn);
+                        delDisc.Parameters.AddWithValue("@id", id);
+                        await delDisc.ExecuteNonQueryAsync();
+                    }
+                    await LoadDisciplinesAsync();
+                    txtDiscName.Clear();
+                    numHours.Value = numHours.Minimum;
+                    MessageBox.Show("Дисциплина и связанные назначения удалены.");
+                }
+                catch (Exception ex)
+                {
+                    MessageBox.Show($"Ошибка: {ex.Message}");
+                }
+                finally
+                {
+                    Cursor = Cursors.Default;
+                }
+            }
+        }
+
+        private void btnExportCsv_Click(object sender, EventArgs e)
+        {
+            if (dgvSchedule.Rows.Count == 0)
+            {
+                MessageBox.Show("Нет данных для экспорта.");
+                return;
+            }
+            using (var sfd = new SaveFileDialog() { Filter = "CSV|*.csv", FileName = "schedule.csv" })
+            {
+                if (sfd.ShowDialog() == DialogResult.OK)
+                {
+                    using (var writer = new StreamWriter(sfd.FileName, false, Encoding.UTF8))
+                    using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
+                    {
+                        foreach (DataGridViewColumn col in dgvSchedule.Columns)
+                            csv.WriteField(col.HeaderText);
+                        csv.NextRecord();
+                        foreach (DataGridViewRow row in dgvSchedule.Rows)
+                        {
+                            if (!row.IsNewRow)
+                            {
+                                foreach (DataGridViewCell cell in row.Cells)
+                                    csv.WriteField(cell.Value?.ToString() ?? "");
+                                csv.NextRecord();
+                            }
+                        }
+                    }
+                    MessageBox.Show("CSV сохранён.");
+                }
+            }
+        }
+
+        private void btnExportHtml_Click(object sender, EventArgs e)
+        {
+            if (dgvSchedule.Rows.Count == 0)
+            {
+                MessageBox.Show("Нет данных для формирования отчёта.");
+                return;
+            }
+
+            using (var sfd = new SaveFileDialog() { Filter = "HTML files (*.html)|*.html", FileName = $"report_{cmbGroups.Text}.html" })
+            {
+                if (sfd.ShowDialog() == DialogResult.OK)
+                {
+                    string groupName = cmbGroups.Text.Trim();
+                    string currentDate = DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss");
+                    int disciplinesCount = dgvSchedule.Rows.Count;
+
+                    StringBuilder sb = new StringBuilder();
+                    sb.AppendLine("<!DOCTYPE html><html><head><meta charset='UTF-8'><title>Отчёт</title>");
+                    sb.AppendLine("<style>table{border-collapse:collapse;width:100%}th,td{border:1px solid #ddd;padding:8px}th{background:#4CAF50;color:#fff}</style>");
+                    sb.AppendLine("</head><body>");
+                    sb.AppendLine($"<h2>Группа {groupName}</h2><p>Дата: {currentDate}</p><p>Всего дисциплин: {disciplinesCount}</p>");
+                    sb.AppendLine("<table border='1'>");
+                    sb.AppendLine("<tr><th>Дисциплина</th><th>Часы</th><th>Преподаватель</th></tr>");
+                    foreach (DataGridViewRow row in dgvSchedule.Rows)
+                    {
+                        if (!row.IsNewRow)
+                        {
+                            sb.AppendLine("<tr>");
+                            foreach (DataGridViewCell cell in row.Cells)
+                                sb.AppendLine($"<td>{cell.Value}</td>");
+                            sb.AppendLine("</tr>");
+                        }
+                    }
+                    sb.AppendLine("</table></body></html>");
+                    File.WriteAllText(sfd.FileName, sb.ToString(), Encoding.UTF8);
+                    MessageBox.Show("HTML-отчёт успешно сохранён!", "Успех", MessageBoxButtons.OK, MessageBoxIcon.Information);
+                }
+            }
+        }
+
+        private void dgvDisciplines_SelectionChanged(object sender, EventArgs e)
+        {
+            if (dgvDisciplines.CurrentRow != null && dgvDisciplines.CurrentRow.Cells["name"].Value != null)
+            {
+                txtDiscName.Text = dgvDisciplines.CurrentRow.Cells["name"].Value.ToString();
+
+                if (dgvDisciplines.CurrentRow.Cells["hours"].Value != null && dgvDisciplines.CurrentRow.Cells["hours"].Value != DBNull.Value)
+                {
+                    int hours = Convert.ToInt32(dgvDisciplines.CurrentRow.Cells["hours"].Value);
+                    if (hours >= numHours.Minimum && hours <= numHours.Maximum)
+                        numHours.Value = hours;
+                    else if (hours < numHours.Minimum)
+                        numHours.Value = numHours.Minimum;
+                    else
+                        numHours.Value = numHours.Maximum;
+                }
+                else
+                    numHours.Value = numHours.Minimum;
+            }
+            else
+            {
+                txtDiscName.Clear();
+                numHours.Value = numHours.Minimum;
+            }
+        }
+
+        private void controlBox_Click(object sender, EventArgs e) => Application.Exit();
+        private void gbDisciplines_Click(object sender, EventArgs e) { }
+        private void panelRight_Paint(object sender, PaintEventArgs e) { }
+        private void cmbTeachers_SelectedIndexChanged(object sender, EventArgs e) { }
+
+        private void btnStudents_Click(object sender, EventArgs e)
+        {
+            if (_userRole == "admin" || _userRole == "manager")
+            {
+                StudentForm studentForm = new StudentForm();
+                studentForm.ShowDialog();
+            }
+            else
+            {
+                MessageBox.Show("Доступ запрещён. Только для администраторов и менеджеров.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+            }
+        }
+
+        private void btnUsers_Click(object sender, EventArgs e)
+        {
+            if (_userRole == "admin")
+            {
+                UserManagementForm f = new UserManagementForm();
+                f.ShowDialog();
+            }
+            else
+            {
+                MessageBox.Show("Доступ запрещён. Только для администратора.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+            }
+        }
+
+        private void btnGrades_Click(object sender, EventArgs e)
+        {
+            if (_userRole == "admin" || _userRole == "manager")
+            {
+                GradeForm gradeForm = new GradeForm();
+                gradeForm.ShowDialog();
+            }
+            else
+            {
+                MessageBox.Show("Доступ запрещён. Только для администраторов и менеджеров.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+            }
+        }
+
+        private void btnAssignments_Click(object sender, EventArgs e)
+        {
+            if (_userRole == "admin" || _userRole == "manager")
+            {
+                AssignmentForm assignmentForm = new AssignmentForm();
+                assignmentForm.ShowDialog();
+            }
+            else
+            {
+                MessageBox.Show("Доступ запрещён. Только для администраторов и менеджеров.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+            }
+        }
+    }
+}

+ 120 - 0
MainForm.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 34 - 0
Program.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Windows.Forms;
+
+namespace DeanOfficeApp
+{
+    static class Program
+    {
+        [STAThread]
+        static void Main()
+        {
+            Application.EnableVisualStyles();
+            Application.SetCompatibleTextRenderingDefault(false);
+            using (var login = new LoginForm())
+            {
+                if (login.ShowDialog() == DialogResult.OK)
+                {
+                    try
+                    {
+                        Application.Run(new MainForm(login.UserRole, login.Username, login.FullName));
+                    }
+                    catch (Exception ex)
+                    {
+                        MessageBox.Show($"Ошибка при открытии главной формы: {ex.Message}\n\n{ex.StackTrace}",
+                                        "Критическая ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
+                    }
+                }
+                else
+                {
+                    Application.Exit();
+                }
+            }
+        }
+    }
+}

+ 33 - 0
Properties/AssemblyInfo.cs

@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// Общие сведения об этой сборке предоставляются следующим набором
+// набора атрибутов. Измените значения этих атрибутов для изменения сведений,
+// связанные с этой сборкой.
+[assembly: AssemblyTitle("ConsoleApp4")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ConsoleApp4")]
+[assembly: AssemblyCopyright("Copyright ©  2026")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Установка значения False для параметра ComVisible делает типы в этой сборке невидимыми
+// для компонентов COM. Если необходимо обратиться к типу в этой сборке через
+// из модели COM задайте для атрибута ComVisible этого типа значение true.
+[assembly: ComVisible(false)]
+
+// Следующий GUID представляет идентификатор typelib, если этот проект доступен из модели COM
+[assembly: Guid("80c1dcb0-9785-452a-846d-faf2ec6c1cd2")]
+
+// Сведения о версии сборки состоят из указанных ниже четырех значений:
+//
+//      Основной номер версии
+//      Дополнительный номер версии
+//      Номер сборки
+//      Номер редакции
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 73 - 0
Properties/app.manifest

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <assemblyIdentity version="1.0.0.0" name="MyApplication.app" />
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+    <security>
+      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+        <!-- Параметры манифеста UAC
+             Если вы хотите изменить уровень контроля учетных записей Windows, замените узел 
+             requestedExecutionLevel на один из следующих.
+
+        <requestedExecutionLevel  level="asInvoker" uiAccess="false" />
+        <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />
+        <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />
+
+            При указании элемента requestedExecutionLevel будет отключена виртуализация файлов и реестра. 
+            Удалите этот элемент, если виртуализация требуется приложению для обратной
+            совместимости.
+        -->
+        <requestedExecutionLevel level="asInvoker" uiAccess="false" />
+      </requestedPrivileges>
+      <applicationRequestMinimum>
+        <PermissionSet class="System.Security.PermissionSet" version="1" Unrestricted="true" ID="Custom" SameSite="site" />
+        <defaultAssemblyRequest permissionSetReference="Custom" />
+      </applicationRequestMinimum>
+    </security>
+  </trustInfo>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- Список версий Windows, на которых это приложение было протестировано
+           и будет работать. Раскомментируйте соответствующие элементы, чтобы ОС Windows 
+           автоматически выбрала наиболее совместимое окружение. -->
+      <!-- Windows Vista -->
+      <!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
+      <!-- Windows 7 -->
+      <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
+      <!-- Windows 8 -->
+      <!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
+      <!-- Windows 8.1 -->
+      <!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
+      <!-- Windows 10 -->
+      <!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
+    </application>
+  </compatibility>
+  <!-- Указывает, что приложение поддерживает определение DPI и не будет автоматически масштабироваться Windows при более высоких
+       значениях DPI. Приложения Windows Presentation Foundation (WPF) по умолчанию поддерживают определение DPI, им не нужно 
+       специально включать параметр для этого. Для приложений Windows Forms на платформе .NET Framework 4.6, для которых задан этот параметр, необходимо 
+       также задать для "EnableWindowsFormsHighDpiAutoResizing" значение "true" в файле app.config. 
+       
+       При этом приложение начинает учитывать длинные пути. Дополнительные сведения см. на странице https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation.-->
+  <!--
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
+      <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+    </windowsSettings>
+  </application>
+  -->
+  <!-- Включите темы для общих элементов управления и диалоговых окон Windows (Windows XP и более поздние версии) -->
+  <!--
+  <dependency>
+    <dependentAssembly>
+      <assemblyIdentity
+          type="win32"
+          name="Microsoft.Windows.Common-Controls"
+          version="6.0.0.0"
+          processorArchitecture="*"
+          publicKeyToken="6595b64144ccf1df"
+          language="*"
+        />
+    </dependentAssembly>
+  </dependency>
+  -->
+</assembly>

+ 333 - 0
StudentForm.cs

@@ -0,0 +1,333 @@
+using CsvHelper;
+using Guna.UI2.WinForms;
+using Npgsql;
+using System;
+using System.Data;
+using System.Globalization;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using System.Linq;
+
+namespace DeanOfficeApp
+{
+    public partial class StudentForm : Form
+    {
+        private Guna2DataGridView dgvStudents;
+        private Guna2Button btnImportCsv;
+        private Guna2ComboBox cmbGroupFilter;
+        private Guna2Button btnRefresh;
+        private Guna2HtmlLabel lblFilter;
+        private Guna2TextBox txtSearch;
+        private DataTable _studentsTable;
+
+        private readonly string _connString = "Host=245e1-rw.db.pub.dbaas.postgrespro.ru;Port=5432;Database=dbpractice;Username=mardasov_mv;Password=A4%2x$$2Ynr";
+
+        public StudentForm()
+        {
+            InitializeComponent();
+            this.Load += async (s, e) => await LoadAllDataAsync();
+        }
+
+        private async Task LoadAllDataAsync()
+        {
+            await LoadGroupsAsync();
+            await LoadStudentsAsync();
+        }
+
+        private async Task LoadGroupsAsync()
+        {
+            try
+            {
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    var cmd = new NpgsqlCommand("SELECT group_id, name FROM \"Group\" ORDER BY name", conn);
+                    var dt = new DataTable();
+                    dt.Load(await cmd.ExecuteReaderAsync());
+                    cmbGroupFilter.DisplayMember = "name";
+                    cmbGroupFilter.ValueMember = "group_id";
+                    cmbGroupFilter.DataSource = dt;
+                    cmbGroupFilter.SelectedIndex = -1;
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки групп: {ex.Message}");
+            }
+        }
+
+        private async Task LoadStudentsAsync(int? groupId = null)
+        {
+            string sql = @"
+                SELECT 
+                    s.record_book,
+                    s.full_name,
+                    g.name AS group_name,
+                    COALESCE(AVG(p.grade), 0) AS average_grade
+                FROM Student s
+                LEFT JOIN ""Group"" g ON s.group_id = g.group_id
+                LEFT JOIN Progress p ON s.record_book = p.record_book
+            ";
+            if (groupId.HasValue)
+                sql += " WHERE g.group_id = @gid";
+            sql += " GROUP BY s.record_book, s.full_name, g.name ORDER BY s.full_name";
+
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        if (groupId.HasValue)
+                            cmd.Parameters.AddWithValue("@gid", groupId.Value);
+                        var dt = new DataTable();
+                        dt.Load(await cmd.ExecuteReaderAsync());
+                        _studentsTable = dt;
+                        ApplyFilter();
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки студентов: {ex.Message}");
+            }
+            finally
+            {
+                Cursor = Cursors.Default;
+            }
+        }
+
+        private void ApplyFilter()
+        {
+            if (_studentsTable == null) return;
+            string filterText = txtSearch.Text.Trim().ToLower();
+            if (string.IsNullOrEmpty(filterText))
+            {
+                dgvStudents.DataSource = _studentsTable;
+            }
+            else
+            {
+                var filteredRows = _studentsTable.AsEnumerable()
+                    .Where(row => row["full_name"].ToString().ToLower().Contains(filterText))
+                    .CopyToDataTable();
+                dgvStudents.DataSource = filteredRows;
+            }
+
+            if (dgvStudents.Columns.Contains("record_book"))
+                dgvStudents.Columns["record_book"].Visible = false;
+            dgvStudents.Columns["average_grade"].HeaderText = "Средний балл";
+            dgvStudents.Columns["average_grade"].DefaultCellStyle.Format = "0.00";
+        }
+
+        private async void btnRefresh_Click(object sender, EventArgs e)
+        {
+            int? groupId = cmbGroupFilter.SelectedValue as int?;
+            await LoadStudentsAsync(groupId);
+        }
+
+        private async void cmbGroupFilter_SelectedIndexChanged(object sender, EventArgs e)
+        {
+            int? groupId = cmbGroupFilter.SelectedValue as int?;
+            await LoadStudentsAsync(groupId);
+        }
+
+        private void txtSearch_TextChanged(object sender, EventArgs e)
+        {
+            ApplyFilter();
+        }
+
+        private async void btnImportCsv_Click(object sender, EventArgs e)
+        {
+            using (OpenFileDialog ofd = new OpenFileDialog())
+            {
+                ofd.Filter = "CSV files (*.csv)|*.csv";
+                if (ofd.ShowDialog() == DialogResult.OK)
+                {
+                    try
+                    {
+                        Cursor = Cursors.WaitCursor;
+                        using (var reader = new StreamReader(ofd.FileName, Encoding.UTF8))
+                        using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
+                        {
+                            var records = csv.GetRecords<dynamic>();
+                            int inserted = 0, skipped = 0;
+                            foreach (var rec in records)
+                            {
+                                string fullName = rec.full_name;
+                                string birthDateStr = rec.birth_date;
+                                string address = rec.address;
+                                int groupId = int.Parse(rec.group_id);
+
+                                using (var conn = new NpgsqlConnection(_connString))
+                                {
+                                    await conn.OpenAsync();
+                                    var check = new NpgsqlCommand("SELECT COUNT(*) FROM Student WHERE full_name = @name AND birth_date = @birth", conn);
+                                    check.Parameters.AddWithValue("@name", fullName);
+                                    check.Parameters.AddWithValue("@birth", DateTime.Parse(birthDateStr));
+                                    long cnt = (long)await check.ExecuteScalarAsync();
+                                    if (cnt == 0)
+                                    {
+                                        var ins = new NpgsqlCommand("INSERT INTO Student (full_name, birth_date, address, group_id) VALUES (@f, @b, @a, @g)", conn);
+                                        ins.Parameters.AddWithValue("@f", fullName);
+                                        ins.Parameters.AddWithValue("@b", DateTime.Parse(birthDateStr));
+                                        ins.Parameters.AddWithValue("@a", address);
+                                        ins.Parameters.AddWithValue("@g", groupId);
+                                        await ins.ExecuteNonQueryAsync();
+                                        inserted++;
+                                    }
+                                    else skipped++;
+                                }
+                            }
+                            MessageBox.Show($"Импорт завершён.\nДобавлено: {inserted}\nПропущено (дубликаты): {skipped}");
+                            await LoadStudentsAsync(cmbGroupFilter.SelectedValue as int?);
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        MessageBox.Show("Ошибка импорта: " + ex.Message);
+                    }
+                    finally
+                    {
+                        Cursor = Cursors.Default;
+                    }
+                }
+            }
+        }
+
+        // Дизайнер
+        private void InitializeComponent()
+        {
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
+            this.dgvStudents = new Guna.UI2.WinForms.Guna2DataGridView();
+            this.btnImportCsv = new Guna.UI2.WinForms.Guna2Button();
+            this.cmbGroupFilter = new Guna.UI2.WinForms.Guna2ComboBox();
+            this.btnRefresh = new Guna.UI2.WinForms.Guna2Button();
+            this.lblFilter = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.txtSearch = new Guna.UI2.WinForms.Guna2TextBox();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvStudents)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // dgvStudents
+            // 
+            this.dgvStudents.AllowUserToAddRows = false;
+            this.dgvStudents.AllowUserToDeleteRows = false;
+            dataGridViewCellStyle1.BackColor = System.Drawing.Color.White;
+            this.dgvStudents.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1;
+            dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle2.BackColor = System.Drawing.Color.FromArgb(100, 88, 255);
+            dataGridViewCellStyle2.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
+            dataGridViewCellStyle2.ForeColor = System.Drawing.Color.White;
+            dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight;
+            dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
+            dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
+            this.dgvStudents.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2;
+            this.dgvStudents.ColumnHeadersHeight = 40;
+            dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle3.BackColor = System.Drawing.Color.White;
+            dataGridViewCellStyle3.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
+            dataGridViewCellStyle3.ForeColor = System.Drawing.Color.FromArgb(71, 69, 94);
+            dataGridViewCellStyle3.SelectionBackColor = System.Drawing.Color.FromArgb(231, 229, 255);
+            dataGridViewCellStyle3.SelectionForeColor = System.Drawing.Color.FromArgb(71, 69, 94);
+            dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
+            this.dgvStudents.DefaultCellStyle = dataGridViewCellStyle3;
+            this.dgvStudents.GridColor = System.Drawing.Color.FromArgb(230, 230, 230);
+            this.dgvStudents.Location = new System.Drawing.Point(20, 60);
+            this.dgvStudents.Name = "dgvStudents";
+            this.dgvStudents.ReadOnly = true;
+            this.dgvStudents.RowHeadersVisible = false;
+            this.dgvStudents.RowTemplate.Height = 30;
+            this.dgvStudents.Size = new System.Drawing.Size(760, 420);
+            this.dgvStudents.TabIndex = 0;
+            // 
+            // btnImportCsv
+            // 
+            this.btnImportCsv.BorderRadius = 10;
+            this.btnImportCsv.FillColor = System.Drawing.Color.FromArgb(40, 167, 69);
+            this.btnImportCsv.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnImportCsv.ForeColor = System.Drawing.Color.White;
+            this.btnImportCsv.Location = new System.Drawing.Point(620, 12);
+            this.btnImportCsv.Name = "btnImportCsv";
+            this.btnImportCsv.Size = new System.Drawing.Size(160, 36);
+            this.btnImportCsv.TabIndex = 1;
+            this.btnImportCsv.Text = "📂 Импорт студентов (CSV)";
+            this.btnImportCsv.Click += new System.EventHandler(this.btnImportCsv_Click);
+            // 
+            // cmbGroupFilter
+            // 
+            this.cmbGroupFilter.BackColor = System.Drawing.Color.Transparent;
+            this.cmbGroupFilter.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
+            this.cmbGroupFilter.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbGroupFilter.FocusedColor = System.Drawing.Color.Empty;
+            this.cmbGroupFilter.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.cmbGroupFilter.ForeColor = System.Drawing.Color.FromArgb(68, 88, 112);
+            this.cmbGroupFilter.ItemHeight = 30;
+            this.cmbGroupFilter.Location = new System.Drawing.Point(120, 12);
+            this.cmbGroupFilter.Name = "cmbGroupFilter";
+            this.cmbGroupFilter.Size = new System.Drawing.Size(180, 36);
+            this.cmbGroupFilter.TabIndex = 2;
+            this.cmbGroupFilter.SelectedIndexChanged += new System.EventHandler(this.cmbGroupFilter_SelectedIndexChanged);
+            // 
+            // btnRefresh
+            // 
+            this.btnRefresh.BorderRadius = 10;
+            this.btnRefresh.FillColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            this.btnRefresh.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnRefresh.ForeColor = System.Drawing.Color.White;
+            this.btnRefresh.Location = new System.Drawing.Point(320, 12);
+            this.btnRefresh.Name = "btnRefresh";
+            this.btnRefresh.Size = new System.Drawing.Size(100, 36);
+            this.btnRefresh.TabIndex = 3;
+            this.btnRefresh.Text = "Обновить";
+            this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click);
+            // 
+            // lblFilter
+            // 
+            this.lblFilter.BackColor = System.Drawing.Color.Transparent;
+            this.lblFilter.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
+            this.lblFilter.Location = new System.Drawing.Point(20, 22);
+            this.lblFilter.Name = "lblFilter";
+            this.lblFilter.Size = new System.Drawing.Size(109, 17);
+            this.lblFilter.TabIndex = 4;
+            this.lblFilter.Text = "Фильтр по группе:";
+            // 
+            // txtSearch
+            // 
+            this.txtSearch.Cursor = System.Windows.Forms.Cursors.IBeam;
+            this.txtSearch.DefaultText = "";
+            this.txtSearch.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.txtSearch.Location = new System.Drawing.Point(440, 12);
+            this.txtSearch.Name = "txtSearch";
+            this.txtSearch.PlaceholderText = "🔍 Поиск по фамилии...";
+            this.txtSearch.Size = new System.Drawing.Size(150, 36);
+            this.txtSearch.TabIndex = 5;
+            this.txtSearch.TextChanged += new System.EventHandler(this.txtSearch_TextChanged);
+            // 
+            // StudentForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.BackColor = System.Drawing.Color.FromArgb(240, 242, 245);
+            this.ClientSize = new System.Drawing.Size(800, 500);
+            this.Controls.Add(this.txtSearch);
+            this.Controls.Add(this.lblFilter);
+            this.Controls.Add(this.btnRefresh);
+            this.Controls.Add(this.cmbGroupFilter);
+            this.Controls.Add(this.btnImportCsv);
+            this.Controls.Add(this.dgvStudents);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+            this.MaximizeBox = false;
+            this.Name = "StudentForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "Управление студентами и средний балл";
+            ((System.ComponentModel.ISupportInitialize)(this.dgvStudents)).EndInit();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+        }
+    }
+}

+ 120 - 0
StudentForm.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 453 - 0
UserManagementForm.cs

@@ -0,0 +1,453 @@
+using Guna.UI2.WinForms;
+using Npgsql;
+using System;
+using System.Data;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace DeanOfficeApp
+{
+    public partial class UserManagementForm : Form
+    {
+        private Guna2DataGridView dgvUsers;
+        private Guna2TextBox txtUsername, txtPassword, txtFullName;
+        private Guna2ComboBox cmbRole;
+        private Guna2Button btnAdd, btnUpdate, btnDelete, btnRefresh;
+        private Guna2HtmlLabel lblUsername, lblPassword, lblFullName, lblRole;
+
+        private readonly string _connString = "Host=245e1-rw.db.pub.dbaas.postgrespro.ru;Port=5432;Database=dbpractice;Username=mardasov_mv;Password=A4%2x$$2Ynr";
+
+        public UserManagementForm()
+        {
+            InitializeComponent();
+            this.Load += async (s, e) => await LoadUsersAsync();
+        }
+
+        // Асинхронная загрузка списка пользователей
+        private async Task LoadUsersAsync()
+        {
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                string sql = "SELECT user_id, username, role, full_name FROM appuser ORDER BY user_id";
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        var dt = new DataTable();
+                        dt.Load(await cmd.ExecuteReaderAsync());
+                        dgvUsers.DataSource = dt;
+                        if (dgvUsers.Columns.Contains("user_id"))
+                            dgvUsers.Columns["user_id"].Visible = false;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка загрузки пользователей: {ex.Message}");
+            }
+            finally
+            {
+                Cursor = Cursors.Default;
+            }
+        }
+
+        // Хеширование пароля через BCrypt
+        private string HashPassword(string password) => BCrypt.Net.BCrypt.HashPassword(password);
+
+        // Добавление нового пользователя
+        private async void btnAdd_Click(object sender, EventArgs e)
+        {
+            string username = txtUsername.Text.Trim();
+            string password = txtPassword.Text.Trim();
+            string fullName = txtFullName.Text.Trim();
+            string role = cmbRole.SelectedItem?.ToString();
+
+            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(role))
+            {
+                MessageBox.Show("Заполните логин, пароль и роль.");
+                return;
+            }
+
+            string hash = HashPassword(password);
+            string sql = "INSERT INTO appuser (username, password_hash, role, full_name) VALUES (@u, @p, @r, @f)";
+
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    using (var cmd = new NpgsqlCommand(sql, conn))
+                    {
+                        cmd.Parameters.AddWithValue("@u", username);
+                        cmd.Parameters.AddWithValue("@p", hash);
+                        cmd.Parameters.AddWithValue("@r", role);
+                        cmd.Parameters.AddWithValue("@f", fullName);
+                        await cmd.ExecuteNonQueryAsync();
+                    }
+                }
+                await LoadUsersAsync();
+                ClearFields();
+                MessageBox.Show("Пользователь добавлен.");
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+            finally
+            {
+                Cursor = Cursors.Default;
+            }
+        }
+
+        // Обновление данных пользователя
+        private async void btnUpdate_Click(object sender, EventArgs e)
+        {
+            if (dgvUsers.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите пользователя для обновления.");
+                return;
+            }
+            int userId = (int)dgvUsers.CurrentRow.Cells["user_id"].Value;
+            string username = txtUsername.Text.Trim();
+            string password = txtPassword.Text.Trim();
+            string fullName = txtFullName.Text.Trim();
+            string role = cmbRole.SelectedItem?.ToString();
+
+            if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(role))
+            {
+                MessageBox.Show("Заполните логин и роль.");
+                return;
+            }
+
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    NpgsqlCommand cmd;
+                    if (!string.IsNullOrEmpty(password))
+                    {
+                        string hash = HashPassword(password);
+                        string sql = "UPDATE appuser SET username=@u, password_hash=@p, role=@r, full_name=@f WHERE user_id=@id";
+                        cmd = new NpgsqlCommand(sql, conn);
+                        cmd.Parameters.AddWithValue("@p", hash);
+                    }
+                    else
+                    {
+                        string sql = "UPDATE appuser SET username=@u, role=@r, full_name=@f WHERE user_id=@id";
+                        cmd = new NpgsqlCommand(sql, conn);
+                    }
+                    cmd.Parameters.AddWithValue("@u", username);
+                    cmd.Parameters.AddWithValue("@r", role);
+                    cmd.Parameters.AddWithValue("@f", fullName);
+                    cmd.Parameters.AddWithValue("@id", userId);
+                    await cmd.ExecuteNonQueryAsync();
+                }
+                await LoadUsersAsync();
+                ClearFields();
+                MessageBox.Show("Пользователь обновлён.");
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+            finally
+            {
+                Cursor = Cursors.Default;
+            }
+        }
+
+        // Удаление пользователя
+        private async void btnDelete_Click(object sender, EventArgs e)
+        {
+            if (dgvUsers.CurrentRow == null)
+            {
+                MessageBox.Show("Выберите пользователя для удаления.");
+                return;
+            }
+            int userId = (int)dgvUsers.CurrentRow.Cells["user_id"].Value;
+            if (MessageBox.Show("Удалить пользователя?", "Подтверждение", MessageBoxButtons.YesNo) != DialogResult.Yes)
+                return;
+
+            try
+            {
+                Cursor = Cursors.WaitCursor;
+                using (var conn = new NpgsqlConnection(_connString))
+                {
+                    await conn.OpenAsync();
+                    var cmd = new NpgsqlCommand("DELETE FROM appuser WHERE user_id=@id", conn);
+                    cmd.Parameters.AddWithValue("@id", userId);
+                    await cmd.ExecuteNonQueryAsync();
+                }
+                await LoadUsersAsync();
+                ClearFields();
+                MessageBox.Show("Пользователь удалён.");
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Ошибка: {ex.Message}");
+            }
+            finally
+            {
+                Cursor = Cursors.Default;
+            }
+        }
+
+        // Обновить список пользователей
+        private async void btnRefresh_Click(object sender, EventArgs e)
+        {
+            await LoadUsersAsync();
+            ClearFields();
+        }
+
+        // Заполнение полей при выборе строки в таблице
+        private void dgvUsers_SelectionChanged(object sender, EventArgs e)
+        {
+            if (dgvUsers.CurrentRow != null)
+            {
+                txtUsername.Text = dgvUsers.CurrentRow.Cells["username"].Value.ToString();
+                txtFullName.Text = dgvUsers.CurrentRow.Cells["full_name"].Value?.ToString() ?? "";
+                string role = dgvUsers.CurrentRow.Cells["role"].Value.ToString();
+                cmbRole.SelectedItem = role;
+                txtPassword.Text = ""; // пароль не отображаем
+            }
+        }
+
+        private void ClearFields()
+        {
+            txtUsername.Clear();
+            txtPassword.Clear();
+            txtFullName.Clear();
+            cmbRole.SelectedIndex = -1;
+        }
+
+        // Пустой обработчик (можно удалить, но оставлен для совместимости)
+        private void UserManagementForm_Load(object sender, EventArgs e) { }
+
+        // ==================== ДИЗАЙНЕР ====================
+        // Ниже расположен полный код InitializeComponent, сгенерированный конструктором.
+        // Он должен быть идентичен вашему существующему, но я привожу его для полноты.
+        private void InitializeComponent()
+        {
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
+            this.dgvUsers = new Guna.UI2.WinForms.Guna2DataGridView();
+            this.txtUsername = new Guna.UI2.WinForms.Guna2TextBox();
+            this.txtPassword = new Guna.UI2.WinForms.Guna2TextBox();
+            this.txtFullName = new Guna.UI2.WinForms.Guna2TextBox();
+            this.cmbRole = new Guna.UI2.WinForms.Guna2ComboBox();
+            this.btnAdd = new Guna.UI2.WinForms.Guna2Button();
+            this.btnUpdate = new Guna.UI2.WinForms.Guna2Button();
+            this.btnDelete = new Guna.UI2.WinForms.Guna2Button();
+            this.btnRefresh = new Guna.UI2.WinForms.Guna2Button();
+            this.lblUsername = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.lblPassword = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.lblFullName = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            this.lblRole = new Guna.UI2.WinForms.Guna2HtmlLabel();
+            ((System.ComponentModel.ISupportInitialize)(this.dgvUsers)).BeginInit();
+            this.SuspendLayout();
+            // 
+            // dgvUsers
+            // 
+            this.dgvUsers.AllowUserToAddRows = false;
+            this.dgvUsers.AllowUserToDeleteRows = false;
+            dataGridViewCellStyle1.BackColor = System.Drawing.Color.White;
+            this.dgvUsers.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1;
+            dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle2.BackColor = System.Drawing.Color.FromArgb(100, 88, 255);
+            dataGridViewCellStyle2.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 204);
+            dataGridViewCellStyle2.ForeColor = System.Drawing.Color.White;
+            dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight;
+            dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
+            dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
+            this.dgvUsers.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle2;
+            this.dgvUsers.ColumnHeadersHeight = 40;
+            dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle3.BackColor = System.Drawing.Color.White;
+            dataGridViewCellStyle3.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 204);
+            dataGridViewCellStyle3.ForeColor = System.Drawing.Color.FromArgb(71, 69, 94);
+            dataGridViewCellStyle3.SelectionBackColor = System.Drawing.Color.FromArgb(231, 229, 255);
+            dataGridViewCellStyle3.SelectionForeColor = System.Drawing.Color.FromArgb(71, 69, 94);
+            dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
+            this.dgvUsers.DefaultCellStyle = dataGridViewCellStyle3;
+            this.dgvUsers.GridColor = System.Drawing.Color.FromArgb(230, 230, 230);
+            this.dgvUsers.Location = new System.Drawing.Point(20, 160);
+            this.dgvUsers.Name = "dgvUsers";
+            this.dgvUsers.ReadOnly = true;
+            this.dgvUsers.RowHeadersVisible = false;
+            this.dgvUsers.RowTemplate.Height = 30;
+            this.dgvUsers.Size = new System.Drawing.Size(760, 320);
+            this.dgvUsers.TabIndex = 0;
+            this.dgvUsers.SelectionChanged += new System.EventHandler(this.dgvUsers_SelectionChanged);
+            // 
+            // txtUsername
+            // 
+            this.txtUsername.Cursor = System.Windows.Forms.Cursors.IBeam;
+            this.txtUsername.DefaultText = "";
+            this.txtUsername.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.txtUsername.Location = new System.Drawing.Point(150, 20);
+            this.txtUsername.Name = "txtUsername";
+            this.txtUsername.PlaceholderText = "Логин";
+            this.txtUsername.Size = new System.Drawing.Size(200, 36);
+            this.txtUsername.TabIndex = 1;
+            // 
+            // txtPassword
+            // 
+            this.txtPassword.Cursor = System.Windows.Forms.Cursors.IBeam;
+            this.txtPassword.DefaultText = "";
+            this.txtPassword.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.txtPassword.Location = new System.Drawing.Point(150, 70);
+            this.txtPassword.Name = "txtPassword";
+            this.txtPassword.PlaceholderText = "Пароль (оставьте пустым, чтобы не менять)";
+            this.txtPassword.Size = new System.Drawing.Size(200, 36);
+            this.txtPassword.TabIndex = 2;
+            // 
+            // txtFullName
+            // 
+            this.txtFullName.Cursor = System.Windows.Forms.Cursors.IBeam;
+            this.txtFullName.DefaultText = "";
+            this.txtFullName.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.txtFullName.Location = new System.Drawing.Point(150, 120);
+            this.txtFullName.Name = "txtFullName";
+            this.txtFullName.PlaceholderText = "Полное имя";
+            this.txtFullName.Size = new System.Drawing.Size(200, 36);
+            this.txtFullName.TabIndex = 3;
+            // 
+            // cmbRole
+            // 
+            this.cmbRole.BackColor = System.Drawing.Color.Transparent;
+            this.cmbRole.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
+            this.cmbRole.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+            this.cmbRole.Font = new System.Drawing.Font("Segoe UI", 10F);
+            this.cmbRole.ForeColor = System.Drawing.Color.FromArgb(68, 88, 112);
+            this.cmbRole.ItemHeight = 30;
+            this.cmbRole.Items.AddRange(new object[] { "admin", "manager", "teacher", "student" });
+            this.cmbRole.Location = new System.Drawing.Point(150, 170);
+            this.cmbRole.Name = "cmbRole";
+            this.cmbRole.Size = new System.Drawing.Size(200, 36);
+            this.cmbRole.TabIndex = 4;
+            // 
+            // btnAdd
+            // 
+            this.btnAdd.BorderRadius = 8;
+            this.btnAdd.FillColor = System.Drawing.Color.FromArgb(40, 167, 69);
+            this.btnAdd.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnAdd.ForeColor = System.Drawing.Color.White;
+            this.btnAdd.Location = new System.Drawing.Point(400, 20);
+            this.btnAdd.Name = "btnAdd";
+            this.btnAdd.Size = new System.Drawing.Size(100, 36);
+            this.btnAdd.TabIndex = 5;
+            this.btnAdd.Text = "Добавить";
+            this.btnAdd.Click += new System.EventHandler(this.btnAdd_Click);
+            // 
+            // btnUpdate
+            // 
+            this.btnUpdate.BorderRadius = 8;
+            this.btnUpdate.FillColor = System.Drawing.Color.FromArgb(255, 193, 7);
+            this.btnUpdate.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnUpdate.ForeColor = System.Drawing.Color.Black;
+            this.btnUpdate.Location = new System.Drawing.Point(520, 20);
+            this.btnUpdate.Name = "btnUpdate";
+            this.btnUpdate.Size = new System.Drawing.Size(100, 36);
+            this.btnUpdate.TabIndex = 6;
+            this.btnUpdate.Text = "Обновить";
+            this.btnUpdate.Click += new System.EventHandler(this.btnUpdate_Click);
+            // 
+            // btnDelete
+            // 
+            this.btnDelete.BorderRadius = 8;
+            this.btnDelete.FillColor = System.Drawing.Color.FromArgb(220, 53, 69);
+            this.btnDelete.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnDelete.ForeColor = System.Drawing.Color.White;
+            this.btnDelete.Location = new System.Drawing.Point(640, 20);
+            this.btnDelete.Name = "btnDelete";
+            this.btnDelete.Size = new System.Drawing.Size(100, 36);
+            this.btnDelete.TabIndex = 7;
+            this.btnDelete.Text = "Удалить";
+            this.btnDelete.Click += new System.EventHandler(this.btnDelete_Click);
+            // 
+            // btnRefresh
+            // 
+            this.btnRefresh.BorderRadius = 8;
+            this.btnRefresh.FillColor = System.Drawing.Color.FromArgb(52, 73, 94);
+            this.btnRefresh.Font = new System.Drawing.Font("Segoe UI", 9F);
+            this.btnRefresh.ForeColor = System.Drawing.Color.White;
+            this.btnRefresh.Location = new System.Drawing.Point(400, 70);
+            this.btnRefresh.Name = "btnRefresh";
+            this.btnRefresh.Size = new System.Drawing.Size(100, 36);
+            this.btnRefresh.TabIndex = 8;
+            this.btnRefresh.Text = "Обновить список";
+            this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click);
+            // 
+            // lblUsername
+            // 
+            this.lblUsername.BackColor = System.Drawing.Color.Transparent;
+            this.lblUsername.Location = new System.Drawing.Point(20, 32);
+            this.lblUsername.Name = "lblUsername";
+            this.lblUsername.Size = new System.Drawing.Size(37, 15);
+            this.lblUsername.TabIndex = 9;
+            this.lblUsername.Text = "Логин:";
+            // 
+            // lblPassword
+            // 
+            this.lblPassword.BackColor = System.Drawing.Color.Transparent;
+            this.lblPassword.Location = new System.Drawing.Point(20, 82);
+            this.lblPassword.Name = "lblPassword";
+            this.lblPassword.Size = new System.Drawing.Size(44, 15);
+            this.lblPassword.TabIndex = 10;
+            this.lblPassword.Text = "Пароль:";
+            // 
+            // lblFullName
+            // 
+            this.lblFullName.BackColor = System.Drawing.Color.Transparent;
+            this.lblFullName.Location = new System.Drawing.Point(20, 132);
+            this.lblFullName.Name = "lblFullName";
+            this.lblFullName.Size = new System.Drawing.Size(33, 15);
+            this.lblFullName.TabIndex = 11;
+            this.lblFullName.Text = "ФИО:";
+            // 
+            // lblRole
+            // 
+            this.lblRole.BackColor = System.Drawing.Color.Transparent;
+            this.lblRole.Location = new System.Drawing.Point(20, 182);
+            this.lblRole.Name = "lblRole";
+            this.lblRole.Size = new System.Drawing.Size(31, 15);
+            this.lblRole.TabIndex = 12;
+            this.lblRole.Text = "Роль:";
+            // 
+            // UserManagementForm
+            // 
+            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+            this.BackColor = System.Drawing.Color.FromArgb(240, 242, 245);
+            this.ClientSize = new System.Drawing.Size(800, 500);
+            this.Controls.Add(this.dgvUsers);
+            this.Controls.Add(this.txtUsername);
+            this.Controls.Add(this.txtPassword);
+            this.Controls.Add(this.txtFullName);
+            this.Controls.Add(this.cmbRole);
+            this.Controls.Add(this.btnAdd);
+            this.Controls.Add(this.btnUpdate);
+            this.Controls.Add(this.btnDelete);
+            this.Controls.Add(this.btnRefresh);
+            this.Controls.Add(this.lblUsername);
+            this.Controls.Add(this.lblPassword);
+            this.Controls.Add(this.lblFullName);
+            this.Controls.Add(this.lblRole);
+            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+            this.MaximizeBox = false;
+            this.Name = "UserManagementForm";
+            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
+            this.Text = "Управление пользователями";
+            this.Load += new System.EventHandler(this.UserManagementForm_Load);
+            ((System.ComponentModel.ISupportInitialize)(this.dgvUsers)).EndInit();
+            this.ResumeLayout(false);
+            this.PerformLayout();
+        }
+    }
+}

+ 120 - 0
UserManagementForm.resx

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 25 - 0
packages.config

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="BCrypt.Net-Next" version="4.2.0" targetFramework="net472" />
+  <package id="Costura.Fody" version="6.1.0" targetFramework="net472" developmentDependency="true" />
+  <package id="CsvHelper" version="33.1.0" targetFramework="net472" />
+  <package id="Fody" version="6.9.3" targetFramework="net472" developmentDependency="true" />
+  <package id="Guna.UI2.WinForms" version="2.0.4.7" targetFramework="net472" />
+  <package id="Microsoft.Bcl.AsyncInterfaces" version="8.0.0" targetFramework="net472" />
+  <package id="Microsoft.Bcl.HashCode" version="1.1.1" targetFramework="net472" />
+  <package id="Microsoft.CSharp" version="4.7.0" targetFramework="net472" />
+  <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="8.0.0" targetFramework="net472" />
+  <package id="Microsoft.Extensions.Logging.Abstractions" version="8.0.0" targetFramework="net472" />
+  <package id="Npgsql" version="8.0.5" targetFramework="net472" />
+  <package id="System.Buffers" version="4.6.1" targetFramework="net472" />
+  <package id="System.Collections.Immutable" version="8.0.0" targetFramework="net472" />
+  <package id="System.Diagnostics.DiagnosticSource" version="8.0.0" targetFramework="net472" />
+  <package id="System.Memory" version="4.6.3" targetFramework="net472" />
+  <package id="System.Numerics.Vectors" version="4.6.1" targetFramework="net472" />
+  <package id="System.Runtime.CompilerServices.Unsafe" version="6.1.2" targetFramework="net472" />
+  <package id="System.Text.Encodings.Web" version="8.0.0" targetFramework="net472" />
+  <package id="System.Text.Json" version="8.0.5" targetFramework="net472" />
+  <package id="System.Threading.Channels" version="8.0.0" targetFramework="net472" />
+  <package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
+  <package id="System.ValueTuple" version="4.5.0" targetFramework="net472" />
+</packages>