MainForm.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. using CsvHelper;
  2. using Npgsql;
  3. using System;
  4. using System.Data;
  5. using System.Globalization;
  6. using System.IO;
  7. using System.Text;
  8. using System.Threading.Tasks;
  9. using System.Windows.Forms;
  10. namespace DeanOfficeApp
  11. {
  12. public partial class MainForm : Form
  13. {
  14. private readonly string _connString = "Host=245e1-rw.db.pub.dbaas.postgrespro.ru;Port=5432;Database=dbpractice;Username=mardasov_mv;Password=A4%2x$$2Ynr";
  15. private readonly string _userRole;
  16. public MainForm(string userRole, string username, string fullName)
  17. {
  18. InitializeComponent();
  19. _userRole = userRole;
  20. this.Text = $"Деканат - {fullName} ({userRole})";
  21. bool canEdit = (userRole == "admin" || userRole == "manager");
  22. gbDisciplines.Visible = canEdit;
  23. btnGrades.Visible = canEdit;
  24. btnAssignments.Visible = canEdit;
  25. numHours.Minimum = 1;
  26. numHours.Maximum = 1000;
  27. numHours.Value = numHours.Minimum;
  28. // Подписываемся на события (если в дизайнере не подписаны)
  29. cmbGroups.SelectedIndexChanged += cmbGroups_SelectedIndexChanged;
  30. rbAsc.CheckedChanged += rbSort_CheckedChanged;
  31. rbDesc.CheckedChanged += rbSort_CheckedChanged;
  32. // Асинхронная загрузка начальных данных
  33. _ = LoadInitialDataAsync();
  34. }
  35. private async Task LoadInitialDataAsync()
  36. {
  37. try
  38. {
  39. Cursor = Cursors.WaitCursor;
  40. await LoadGroupsAsync();
  41. if (_userRole == "admin" || _userRole == "manager")
  42. {
  43. await LoadDisciplinesAsync();
  44. await LoadTeachersAsync();
  45. }
  46. }
  47. finally
  48. {
  49. Cursor = Cursors.Default;
  50. }
  51. }
  52. private async Task LoadGroupsAsync()
  53. {
  54. try
  55. {
  56. using (var conn = new NpgsqlConnection(_connString))
  57. {
  58. await conn.OpenAsync();
  59. var cmd = new NpgsqlCommand("SELECT group_id, name FROM \"Group\" ORDER BY name", conn);
  60. var dt = new DataTable();
  61. dt.Load(await cmd.ExecuteReaderAsync());
  62. cmbGroups.DisplayMember = "name";
  63. cmbGroups.ValueMember = "group_id";
  64. cmbGroups.DataSource = dt;
  65. if (cmbGroups.Items.Count > 0)
  66. cmbGroups.SelectedIndex = 0; // автоматически выбрать первую группу
  67. }
  68. }
  69. catch (Exception ex)
  70. {
  71. MessageBox.Show($"Ошибка загрузки групп: {ex.Message}");
  72. }
  73. }
  74. private async Task LoadTeachersAsync()
  75. {
  76. try
  77. {
  78. using (var conn = new NpgsqlConnection(_connString))
  79. {
  80. await conn.OpenAsync();
  81. var cmd = new NpgsqlCommand("SELECT teacher_id, full_name FROM Teacher ORDER BY full_name", conn);
  82. var dt = new DataTable();
  83. dt.Load(await cmd.ExecuteReaderAsync());
  84. cmbTeachers.DisplayMember = "full_name";
  85. cmbTeachers.ValueMember = "teacher_id";
  86. cmbTeachers.DataSource = dt;
  87. if (cmbTeachers.Items.Count > 0)
  88. cmbTeachers.SelectedIndex = 0;
  89. }
  90. }
  91. catch (Exception ex)
  92. {
  93. MessageBox.Show($"Ошибка загрузки преподавателей: {ex.Message}");
  94. }
  95. }
  96. private async Task LoadDisciplinesAsync()
  97. {
  98. try
  99. {
  100. using (var conn = new NpgsqlConnection(_connString))
  101. {
  102. await conn.OpenAsync();
  103. var cmd = new NpgsqlCommand("SELECT discipline_id, name, hours FROM Discipline ORDER BY name", conn);
  104. var dt = new DataTable();
  105. dt.Load(await cmd.ExecuteReaderAsync());
  106. dgvDisciplines.DataSource = dt;
  107. if (dgvDisciplines.Columns.Contains("discipline_id"))
  108. dgvDisciplines.Columns["discipline_id"].Visible = false;
  109. }
  110. dgvDisciplines.ClearSelection();
  111. txtDiscName.Clear();
  112. numHours.Value = numHours.Minimum;
  113. await RefreshScheduleAsync();
  114. }
  115. catch (Exception ex)
  116. {
  117. MessageBox.Show($"Ошибка загрузки дисциплин: {ex.Message}");
  118. }
  119. }
  120. private async Task RefreshScheduleAsync()
  121. {
  122. if (cmbGroups.SelectedValue != null)
  123. await LoadScheduleAsync((int)cmbGroups.SelectedValue);
  124. }
  125. private async Task LoadScheduleAsync(int groupId)
  126. {
  127. // Безопасная сортировка: проверяем, что выбранное значение допустимо
  128. string sortDirection = rbAsc.Checked ? "ASC" : "DESC";
  129. // Используем параметризованный запрос, но ORDER BY нельзя параметризовать,
  130. // поэтому проверяем значение перед вставкой.
  131. if (sortDirection != "ASC" && sortDirection != "DESC")
  132. sortDirection = "ASC";
  133. string query = $@"
  134. SELECT d.name AS Дисциплина, d.hours AS Часы, t.full_name AS Преподаватель
  135. FROM Assignment a
  136. JOIN Discipline d ON a.discipline_id = d.discipline_id
  137. JOIN Teacher t ON a.teacher_id = t.teacher_id
  138. WHERE a.group_id = @gid
  139. ORDER BY d.hours {sortDirection}";
  140. try
  141. {
  142. Cursor = Cursors.WaitCursor;
  143. using (var conn = new NpgsqlConnection(_connString))
  144. {
  145. await conn.OpenAsync();
  146. using (var cmd = new NpgsqlCommand(query, conn))
  147. {
  148. cmd.Parameters.AddWithValue("@gid", groupId);
  149. var dt = new DataTable();
  150. dt.Load(await cmd.ExecuteReaderAsync());
  151. dgvSchedule.DataSource = dt;
  152. }
  153. }
  154. }
  155. catch (Exception ex)
  156. {
  157. MessageBox.Show($"Ошибка загрузки расписания: {ex.Message}");
  158. }
  159. finally
  160. {
  161. Cursor = Cursors.Default;
  162. }
  163. }
  164. // Обработчик выбора группы
  165. private async void cmbGroups_SelectedIndexChanged(object sender, EventArgs e)
  166. {
  167. await RefreshScheduleAsync();
  168. }
  169. // Обработчик изменения сортировки
  170. private async void rbSort_CheckedChanged(object sender, EventArgs e)
  171. {
  172. await RefreshScheduleAsync();
  173. }
  174. // Кнопка "Загрузить расписание" (можно оставить для ручного обновления)
  175. private async void btnLoad_Click(object sender, EventArgs e)
  176. {
  177. await RefreshScheduleAsync();
  178. }
  179. private async void btnAdd_Click(object sender, EventArgs e)
  180. {
  181. if (string.IsNullOrWhiteSpace(txtDiscName.Text))
  182. {
  183. MessageBox.Show("Введите название дисциплины.");
  184. return;
  185. }
  186. if (numHours.Value < numHours.Minimum)
  187. {
  188. MessageBox.Show($"Количество часов должно быть не менее {numHours.Minimum}.");
  189. return;
  190. }
  191. if (cmbGroups.SelectedValue == null)
  192. {
  193. MessageBox.Show("Выберите группу, к которой будет привязана дисциплина.");
  194. return;
  195. }
  196. if (cmbTeachers.SelectedValue == null)
  197. {
  198. MessageBox.Show("Выберите преподавателя.");
  199. return;
  200. }
  201. int groupId = (int)cmbGroups.SelectedValue;
  202. int teacherId = (int)cmbTeachers.SelectedValue;
  203. string discName = txtDiscName.Text.Trim();
  204. int hours = (int)numHours.Value;
  205. try
  206. {
  207. Cursor = Cursors.WaitCursor;
  208. using (var conn = new NpgsqlConnection(_connString))
  209. {
  210. await conn.OpenAsync();
  211. var cmdInsert = new NpgsqlCommand(
  212. "INSERT INTO Discipline (name, hours) VALUES (@n, @h) RETURNING discipline_id", conn);
  213. cmdInsert.Parameters.AddWithValue("@n", discName);
  214. cmdInsert.Parameters.AddWithValue("@h", hours);
  215. int newDisciplineId = (int)await cmdInsert.ExecuteScalarAsync();
  216. var cmdAssign = new NpgsqlCommand(
  217. "INSERT INTO Assignment (group_id, discipline_id, teacher_id, semester) VALUES (@g, @d, @t, 1)", conn);
  218. cmdAssign.Parameters.AddWithValue("@g", groupId);
  219. cmdAssign.Parameters.AddWithValue("@d", newDisciplineId);
  220. cmdAssign.Parameters.AddWithValue("@t", teacherId);
  221. await cmdAssign.ExecuteNonQueryAsync();
  222. }
  223. await LoadDisciplinesAsync();
  224. txtDiscName.Clear();
  225. numHours.Value = numHours.Minimum;
  226. MessageBox.Show("Дисциплина добавлена и привязана к группе и преподавателю.");
  227. }
  228. catch (Exception ex)
  229. {
  230. MessageBox.Show($"Ошибка: {ex.Message}");
  231. }
  232. finally
  233. {
  234. Cursor = Cursors.Default;
  235. }
  236. }
  237. private async void btnUpdate_Click(object sender, EventArgs e)
  238. {
  239. if (dgvDisciplines.CurrentRow == null)
  240. {
  241. MessageBox.Show("Выберите дисциплину для обновления.");
  242. return;
  243. }
  244. int id = (int)dgvDisciplines.CurrentRow.Cells["discipline_id"].Value;
  245. if (string.IsNullOrWhiteSpace(txtDiscName.Text))
  246. {
  247. MessageBox.Show("Введите название.");
  248. return;
  249. }
  250. if (numHours.Value < numHours.Minimum)
  251. {
  252. MessageBox.Show($"Количество часов должно быть не менее {numHours.Minimum}.");
  253. return;
  254. }
  255. try
  256. {
  257. Cursor = Cursors.WaitCursor;
  258. using (var conn = new NpgsqlConnection(_connString))
  259. {
  260. await conn.OpenAsync();
  261. var cmd = new NpgsqlCommand("UPDATE Discipline SET name=@n, hours=@h WHERE discipline_id=@id", conn);
  262. cmd.Parameters.AddWithValue("@n", txtDiscName.Text.Trim());
  263. cmd.Parameters.AddWithValue("@h", (int)numHours.Value);
  264. cmd.Parameters.AddWithValue("@id", id);
  265. await cmd.ExecuteNonQueryAsync();
  266. }
  267. await LoadDisciplinesAsync();
  268. MessageBox.Show("Дисциплина обновлена.");
  269. }
  270. catch (Exception ex)
  271. {
  272. MessageBox.Show($"Ошибка: {ex.Message}");
  273. }
  274. finally
  275. {
  276. Cursor = Cursors.Default;
  277. }
  278. }
  279. private async void btnDelete_Click(object sender, EventArgs e)
  280. {
  281. if (dgvDisciplines.CurrentRow == null)
  282. {
  283. MessageBox.Show("Выберите дисциплину для удаления.");
  284. return;
  285. }
  286. int id = (int)dgvDisciplines.CurrentRow.Cells["discipline_id"].Value;
  287. if (MessageBox.Show("Удалить дисциплину? Это также удалит все её связи (Assignment).",
  288. "Подтверждение", MessageBoxButtons.YesNo) == DialogResult.Yes)
  289. {
  290. try
  291. {
  292. Cursor = Cursors.WaitCursor;
  293. using (var conn = new NpgsqlConnection(_connString))
  294. {
  295. await conn.OpenAsync();
  296. var delAssign = new NpgsqlCommand("DELETE FROM Assignment WHERE discipline_id = @id", conn);
  297. delAssign.Parameters.AddWithValue("@id", id);
  298. await delAssign.ExecuteNonQueryAsync();
  299. var delDisc = new NpgsqlCommand("DELETE FROM Discipline WHERE discipline_id = @id", conn);
  300. delDisc.Parameters.AddWithValue("@id", id);
  301. await delDisc.ExecuteNonQueryAsync();
  302. }
  303. await LoadDisciplinesAsync();
  304. txtDiscName.Clear();
  305. numHours.Value = numHours.Minimum;
  306. MessageBox.Show("Дисциплина и связанные назначения удалены.");
  307. }
  308. catch (Exception ex)
  309. {
  310. MessageBox.Show($"Ошибка: {ex.Message}");
  311. }
  312. finally
  313. {
  314. Cursor = Cursors.Default;
  315. }
  316. }
  317. }
  318. private void btnExportCsv_Click(object sender, EventArgs e)
  319. {
  320. if (dgvSchedule.Rows.Count == 0)
  321. {
  322. MessageBox.Show("Нет данных для экспорта.");
  323. return;
  324. }
  325. using (var sfd = new SaveFileDialog() { Filter = "CSV|*.csv", FileName = "schedule.csv" })
  326. {
  327. if (sfd.ShowDialog() == DialogResult.OK)
  328. {
  329. using (var writer = new StreamWriter(sfd.FileName, false, Encoding.UTF8))
  330. using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
  331. {
  332. foreach (DataGridViewColumn col in dgvSchedule.Columns)
  333. csv.WriteField(col.HeaderText);
  334. csv.NextRecord();
  335. foreach (DataGridViewRow row in dgvSchedule.Rows)
  336. {
  337. if (!row.IsNewRow)
  338. {
  339. foreach (DataGridViewCell cell in row.Cells)
  340. csv.WriteField(cell.Value?.ToString() ?? "");
  341. csv.NextRecord();
  342. }
  343. }
  344. }
  345. MessageBox.Show("CSV сохранён.");
  346. }
  347. }
  348. }
  349. private void btnExportHtml_Click(object sender, EventArgs e)
  350. {
  351. if (dgvSchedule.Rows.Count == 0)
  352. {
  353. MessageBox.Show("Нет данных для формирования отчёта.");
  354. return;
  355. }
  356. using (var sfd = new SaveFileDialog() { Filter = "HTML files (*.html)|*.html", FileName = $"report_{cmbGroups.Text}.html" })
  357. {
  358. if (sfd.ShowDialog() == DialogResult.OK)
  359. {
  360. string groupName = cmbGroups.Text.Trim();
  361. string currentDate = DateTime.Now.ToString("dd.MM.yyyy HH:mm:ss");
  362. int disciplinesCount = dgvSchedule.Rows.Count;
  363. StringBuilder sb = new StringBuilder();
  364. sb.AppendLine("<!DOCTYPE html><html><head><meta charset='UTF-8'><title>Отчёт</title>");
  365. sb.AppendLine("<style>table{border-collapse:collapse;width:100%}th,td{border:1px solid #ddd;padding:8px}th{background:#4CAF50;color:#fff}</style>");
  366. sb.AppendLine("</head><body>");
  367. sb.AppendLine($"<h2>Группа {groupName}</h2><p>Дата: {currentDate}</p><p>Всего дисциплин: {disciplinesCount}</p>");
  368. sb.AppendLine("<table border='1'>");
  369. sb.AppendLine("<tr><th>Дисциплина</th><th>Часы</th><th>Преподаватель</th></tr>");
  370. foreach (DataGridViewRow row in dgvSchedule.Rows)
  371. {
  372. if (!row.IsNewRow)
  373. {
  374. sb.AppendLine("<tr>");
  375. foreach (DataGridViewCell cell in row.Cells)
  376. sb.AppendLine($"<td>{cell.Value}</td>");
  377. sb.AppendLine("</tr>");
  378. }
  379. }
  380. sb.AppendLine("</table></body></html>");
  381. File.WriteAllText(sfd.FileName, sb.ToString(), Encoding.UTF8);
  382. MessageBox.Show("HTML-отчёт успешно сохранён!", "Успех", MessageBoxButtons.OK, MessageBoxIcon.Information);
  383. }
  384. }
  385. }
  386. private void dgvDisciplines_SelectionChanged(object sender, EventArgs e)
  387. {
  388. if (dgvDisciplines.CurrentRow != null && dgvDisciplines.CurrentRow.Cells["name"].Value != null)
  389. {
  390. txtDiscName.Text = dgvDisciplines.CurrentRow.Cells["name"].Value.ToString();
  391. if (dgvDisciplines.CurrentRow.Cells["hours"].Value != null && dgvDisciplines.CurrentRow.Cells["hours"].Value != DBNull.Value)
  392. {
  393. int hours = Convert.ToInt32(dgvDisciplines.CurrentRow.Cells["hours"].Value);
  394. if (hours >= numHours.Minimum && hours <= numHours.Maximum)
  395. numHours.Value = hours;
  396. else if (hours < numHours.Minimum)
  397. numHours.Value = numHours.Minimum;
  398. else
  399. numHours.Value = numHours.Maximum;
  400. }
  401. else
  402. numHours.Value = numHours.Minimum;
  403. }
  404. else
  405. {
  406. txtDiscName.Clear();
  407. numHours.Value = numHours.Minimum;
  408. }
  409. }
  410. private void controlBox_Click(object sender, EventArgs e) => Application.Exit();
  411. private void gbDisciplines_Click(object sender, EventArgs e) { }
  412. private void panelRight_Paint(object sender, PaintEventArgs e) { }
  413. private void cmbTeachers_SelectedIndexChanged(object sender, EventArgs e) { }
  414. private void btnStudents_Click(object sender, EventArgs e)
  415. {
  416. if (_userRole == "admin" || _userRole == "manager")
  417. {
  418. StudentForm studentForm = new StudentForm();
  419. studentForm.ShowDialog();
  420. }
  421. else
  422. {
  423. MessageBox.Show("Доступ запрещён. Только для администраторов и менеджеров.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
  424. }
  425. }
  426. private void btnUsers_Click(object sender, EventArgs e)
  427. {
  428. if (_userRole == "admin")
  429. {
  430. UserManagementForm f = new UserManagementForm();
  431. f.ShowDialog();
  432. }
  433. else
  434. {
  435. MessageBox.Show("Доступ запрещён. Только для администратора.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
  436. }
  437. }
  438. private void btnGrades_Click(object sender, EventArgs e)
  439. {
  440. if (_userRole == "admin" || _userRole == "manager")
  441. {
  442. GradeForm gradeForm = new GradeForm();
  443. gradeForm.ShowDialog();
  444. }
  445. else
  446. {
  447. MessageBox.Show("Доступ запрещён. Только для администраторов и менеджеров.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
  448. }
  449. }
  450. private void btnAssignments_Click(object sender, EventArgs e)
  451. {
  452. if (_userRole == "admin" || _userRole == "manager")
  453. {
  454. AssignmentForm assignmentForm = new AssignmentForm();
  455. assignmentForm.ShowDialog();
  456. }
  457. else
  458. {
  459. MessageBox.Show("Доступ запрещён. Только для администраторов и менеджеров.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
  460. }
  461. }
  462. }
  463. }