MainForm.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Data;
  5. using System.Drawing;
  6. using System.IO;
  7. using System.Text;
  8. using System.Windows.Forms;
  9. using Npgsql;
  10. using NpgsqlTypes;
  11. namespace SimpleDBViewer
  12. {
  13. public partial class MainForm : Form
  14. {
  15. public const string DbSchema = "akulova_vs";
  16. private readonly List<TableInfo> tableInfos = new List<TableInfo>();
  17. private TableInfo currentTable;
  18. private DataTable currentTableData;
  19. private DataTable currentSearchData;
  20. public MainForm()
  21. {
  22. bool isDesigner = LicenseManager.UsageMode == LicenseUsageMode.Designtime;
  23. if (!isDesigner && !UserSession.IsLoggedIn)
  24. {
  25. MessageBox.Show("Сначала выполните вход в систему.", "Доступ запрещён",
  26. MessageBoxButtons.OK, MessageBoxIcon.Warning);
  27. Close();
  28. return;
  29. }
  30. InitializeComponent();
  31. if (isDesigner)
  32. return;
  33. BuildTableList();
  34. ApplyPermissions();
  35. LoadInitialData();
  36. }
  37. private void BuildTableList()
  38. {
  39. tableInfos.Clear();
  40. if (!UserSession.IsAdmin)
  41. {
  42. tableInfos.Add(new TableInfo("personal_profile", "Мои данные", "driver_id"));
  43. tableInfos.Add(new TableInfo("personal_exams", "Мои экзамены", "exam_id"));
  44. tableInfos.Add(new TableInfo("personal_license", "Моё удостоверение", "license_id"));
  45. return;
  46. }
  47. tableInfos.Add(new TableInfo("driving_schools", "Автошколы", "school_id"));
  48. tableInfos.Add(new TableInfo("instructors", "Инструкторы", "instructor_id"));
  49. tableInfos.Add(new TableInfo("drivers", "Водители", "driver_id"));
  50. tableInfos.Add(new TableInfo("exam_routes", "Маршруты экзамена", "route_id"));
  51. tableInfos.Add(new TableInfo("traffic_inspectors", "Инспекторы", "inspector_id"));
  52. tableInfos.Add(new TableInfo("traffic_officers", "Сотрудники, выдающие права", "officer_id"));
  53. tableInfos.Add(new TableInfo("exams", "Экзамены", "exam_id"));
  54. tableInfos.Add(new TableInfo("licenses", "Водительские удостоверения", "license_id"));
  55. tableInfos.Add(new TableInfo("app_users", "Пользователи приложения", "user_id"));
  56. tableInfos.Add(new TableInfo("audit_log", "Журнал операций", "log_id", true));
  57. }
  58. private void ApplyPermissions()
  59. {
  60. lblUser.Text = UserSession.CurrentUser.Login + " · " + UserSession.CurrentUser.Role;
  61. bool isAdmin = UserSession.IsAdmin;
  62. btnAdd.Enabled = isAdmin;
  63. btnEdit.Enabled = isAdmin;
  64. btnDelete.Enabled = isAdmin;
  65. if (!isAdmin)
  66. {
  67. lblTitle.Text = "Личный кабинет автошколы";
  68. lblTableCaption.Text = "Раздел:";
  69. tabTables.Text = "Личный кабинет";
  70. btnAdd.Visible = false;
  71. btnEdit.Visible = false;
  72. btnDelete.Visible = false;
  73. btnExportTable.Location = btnAdd.Location;
  74. if (tabControl.TabPages.Contains(tabSearch))
  75. tabControl.TabPages.Remove(tabSearch);
  76. }
  77. UpdateActionButtons();
  78. }
  79. private void UpdateActionButtons()
  80. {
  81. bool canModify = UserSession.IsAdmin && currentTable != null && !currentTable.IsReadOnly;
  82. if (btnAdd != null) btnAdd.Enabled = canModify;
  83. if (btnEdit != null) btnEdit.Enabled = canModify;
  84. if (btnDelete != null) btnDelete.Enabled = canModify;
  85. }
  86. private void LoadInitialData()
  87. {
  88. cmbTables.Items.Clear();
  89. foreach (TableInfo table in tableInfos)
  90. {
  91. cmbTables.Items.Add(table);
  92. }
  93. if (cmbTables.Items.Count > 0)
  94. {
  95. cmbTables.SelectedIndex = 0;
  96. }
  97. if (UserSession.IsAdmin)
  98. LoadExamSearch();
  99. }
  100. private void CmbTables_SelectedIndexChanged(object sender, EventArgs e)
  101. {
  102. currentTable = cmbTables.SelectedItem as TableInfo;
  103. UpdateActionButtons();
  104. LoadCurrentTable();
  105. }
  106. private void LoadCurrentTable()
  107. {
  108. if (currentTable == null)
  109. return;
  110. try
  111. {
  112. using (NpgsqlConnection connection = new NpgsqlConnection(DbSettings.ConnectionString))
  113. {
  114. connection.Open();
  115. string sql = GetSelectSql(currentTable);
  116. using (NpgsqlCommand command = new NpgsqlCommand(sql, connection))
  117. {
  118. AddCurrentTableParameters(command);
  119. using (NpgsqlDataAdapter adapter = new NpgsqlDataAdapter(command))
  120. {
  121. currentTableData = new DataTable();
  122. adapter.Fill(currentTableData);
  123. dgvTables.DataSource = currentTableData;
  124. ApplyGridPresentation(dgvTables);
  125. lblTableStatus.Text = currentTable.DisplayName + ": записей — " + currentTableData.Rows.Count;
  126. }
  127. }
  128. }
  129. }
  130. catch (Exception ex)
  131. {
  132. MessageBox.Show(AppError.ToUserMessage(ex, "Не удалось загрузить таблицу."),
  133. "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
  134. }
  135. }
  136. private void AddCurrentTableParameters(NpgsqlCommand command)
  137. {
  138. if (currentTable == null)
  139. return;
  140. if (currentTable.Name == "personal_profile" || currentTable.Name == "personal_exams" || currentTable.Name == "personal_license")
  141. {
  142. int driverId = UserSession.CurrentUser != null && UserSession.CurrentUser.DriverId.HasValue
  143. ? UserSession.CurrentUser.DriverId.Value
  144. : -1;
  145. command.Parameters.AddWithValue("@current_driver_id", driverId);
  146. }
  147. }
  148. private string GetSelectSql(TableInfo table)
  149. {
  150. if (table.Name == "personal_profile")
  151. {
  152. if (GetCurrentDriverCondition() == null)
  153. return "select 'Учётная запись не привязана к водителю. Обратитесь к администратору.' as message";
  154. return @"select d.driver_id,
  155. concat_ws(' ', d.last_name, d.first_name, d.middle_name) as driver_display,
  156. d.birth_date,
  157. d.phone,
  158. ds.school_name
  159. from akulova_vs.drivers d
  160. join akulova_vs.driving_schools ds on ds.school_id = d.school_id
  161. where d.driver_id = @current_driver_id";
  162. }
  163. if (table.Name == "personal_exams")
  164. {
  165. if (GetCurrentDriverCondition() == null)
  166. return "select 'Учётная запись не привязана к водителю. Обратитесь к администратору.' as message";
  167. return @"select exam_id,
  168. exam_date,
  169. result,
  170. route_name,
  171. inspector_full_name,
  172. attempt_number,
  173. driver_age
  174. from (
  175. select e.exam_id,
  176. e.exam_date,
  177. e.result,
  178. r.route_name,
  179. concat_ws(' ', ti.last_name, ti.first_name, ti.middle_name) as inspector_full_name,
  180. count(*) over (
  181. partition by e.driver_id
  182. order by e.exam_date, e.exam_id
  183. rows between unbounded preceding and current row
  184. )::integer as attempt_number,
  185. extract(year from age(e.exam_date, d.birth_date))::integer as driver_age
  186. from akulova_vs.exams e
  187. join akulova_vs.drivers d on d.driver_id = e.driver_id
  188. join akulova_vs.exam_routes r on r.route_id = e.route_id
  189. join akulova_vs.traffic_inspectors ti on ti.inspector_id = e.inspector_id
  190. where e.driver_id = @current_driver_id
  191. ) personal_exams
  192. order by exam_date desc, exam_id desc";
  193. }
  194. if (table.Name == "personal_license")
  195. {
  196. if (GetCurrentDriverCondition() == null)
  197. return "select 'Учётная запись не привязана к водителю. Обратитесь к администратору.' as message";
  198. return @"select l.license_id,
  199. l.issue_date,
  200. l.category,
  201. l.license_number,
  202. concat_ws(' ', o.last_name, o.first_name, o.middle_name) as officer_display
  203. from akulova_vs.licenses l
  204. join akulova_vs.traffic_officers o on o.officer_id = l.officer_id
  205. where l.driver_id = @current_driver_id
  206. order by l.issue_date desc, l.license_id desc";
  207. }
  208. if (table.Name == "driving_schools")
  209. {
  210. return @"select school_id, school_name, address, phone
  211. from akulova_vs.driving_schools
  212. order by school_id";
  213. }
  214. if (table.Name == "instructors")
  215. {
  216. return @"select i.instructor_id, i.school_id, ds.school_name as school_display,
  217. i.last_name, i.first_name, i.middle_name, i.phone, i.license_number
  218. from akulova_vs.instructors i
  219. join akulova_vs.driving_schools ds on ds.school_id = i.school_id
  220. order by i.instructor_id";
  221. }
  222. if (table.Name == "drivers")
  223. {
  224. return @"select d.driver_id, d.school_id, ds.school_name as school_display,
  225. d.last_name, d.first_name, d.middle_name, d.birth_date, d.phone
  226. from akulova_vs.drivers d
  227. join akulova_vs.driving_schools ds on ds.school_id = d.school_id
  228. order by d.driver_id";
  229. }
  230. if (table.Name == "exam_routes")
  231. {
  232. return @"select route_id, route_name, start_address, end_address, difficulty
  233. from akulova_vs.exam_routes
  234. order by route_id";
  235. }
  236. if (table.Name == "traffic_inspectors")
  237. {
  238. return @"select inspector_id, last_name, first_name, middle_name, rank_name
  239. from akulova_vs.traffic_inspectors
  240. order by inspector_id";
  241. }
  242. if (table.Name == "traffic_officers")
  243. {
  244. return @"select officer_id, last_name, first_name, middle_name, position_name
  245. from akulova_vs.traffic_officers
  246. order by officer_id";
  247. }
  248. if (table.Name == "exams")
  249. {
  250. return @"select e.exam_id,
  251. e.driver_id, concat_ws(' ', d.last_name, d.first_name, d.middle_name) as driver_display,
  252. e.route_id, r.route_name as route_display,
  253. e.inspector_id, concat_ws(' ', ti.last_name, ti.first_name, ti.middle_name) as inspector_display,
  254. e.exam_date, e.result
  255. from akulova_vs.exams e
  256. join akulova_vs.drivers d on d.driver_id = e.driver_id
  257. join akulova_vs.exam_routes r on r.route_id = e.route_id
  258. join akulova_vs.traffic_inspectors ti on ti.inspector_id = e.inspector_id
  259. order by e.exam_id";
  260. }
  261. if (table.Name == "licenses")
  262. {
  263. return @"select l.license_id,
  264. l.driver_id, concat_ws(' ', d.last_name, d.first_name, d.middle_name) as driver_display,
  265. l.officer_id, concat_ws(' ', o.last_name, o.first_name, o.middle_name) as officer_display,
  266. l.issue_date, l.category, l.license_number
  267. from akulova_vs.licenses l
  268. join akulova_vs.drivers d on d.driver_id = l.driver_id
  269. join akulova_vs.traffic_officers o on o.officer_id = l.officer_id
  270. order by l.license_id";
  271. }
  272. if (table.Name == "app_users")
  273. {
  274. return @"select u.user_id, u.login, u.role::text as role, u.is_active,
  275. u.instructor_id, concat_ws(' ', i.last_name, i.first_name, i.middle_name) as instructor_display,
  276. u.driver_id, concat_ws(' ', d.last_name, d.first_name, d.middle_name) as driver_display
  277. from akulova_vs.app_users u
  278. left join akulova_vs.instructors i on i.instructor_id = u.instructor_id
  279. left join akulova_vs.drivers d on d.driver_id = u.driver_id
  280. order by u.user_id";
  281. }
  282. if (table.Name == "audit_log")
  283. {
  284. return @"select log_id,
  285. table_name,
  286. operation,
  287. record_id,
  288. changed_at,
  289. coalesce(nullif(changed_by, ''), 'system') as changed_by
  290. from akulova_vs.audit_log
  291. order by changed_at desc, log_id desc
  292. limit 500";
  293. }
  294. return "select * from " + FullName(table.Name) + " order by " + Quote(table.PrimaryKey);
  295. }
  296. private string GetCurrentDriverCondition()
  297. {
  298. if (UserSession.CurrentUser == null || !UserSession.CurrentUser.DriverId.HasValue)
  299. return null;
  300. return UserSession.CurrentUser.DriverId.Value.ToString();
  301. }
  302. private void ApplyGridPresentation(DataGridView grid)
  303. {
  304. if (grid == null)
  305. return;
  306. foreach (DataGridViewColumn column in grid.Columns)
  307. {
  308. string name = column.Name;
  309. column.Visible = !IsTechnicalColumn(name);
  310. column.HeaderText = GetColumnCaption(name);
  311. if (name.EndsWith("_date", StringComparison.OrdinalIgnoreCase) || name == "birth_date" || name == "exam_date" || name == "issue_date")
  312. column.DefaultCellStyle.Format = "dd.MM.yyyy";
  313. }
  314. grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
  315. grid.AutoResizeColumns();
  316. }
  317. private bool IsTechnicalColumn(string columnName)
  318. {
  319. if (string.IsNullOrEmpty(columnName))
  320. return false;
  321. if (columnName == "password_hash")
  322. return true;
  323. if (columnName == "record_id")
  324. return false;
  325. if (columnName.EndsWith("_id", StringComparison.OrdinalIgnoreCase))
  326. return true;
  327. return false;
  328. }
  329. private string GetColumnCaption(string columnName)
  330. {
  331. Dictionary<string, string> captions = new Dictionary<string, string>
  332. {
  333. { "school_name", "Название автошколы" },
  334. { "address", "Адрес" },
  335. { "phone", "Телефон" },
  336. { "school_display", "Автошкола" },
  337. { "last_name", "Фамилия" },
  338. { "first_name", "Имя" },
  339. { "middle_name", "Отчество" },
  340. { "license_number", "Номер удостоверения" },
  341. { "birth_date", "Дата рождения" },
  342. { "route_name", "Название маршрута" },
  343. { "route_display", "Маршрут" },
  344. { "start_address", "Начальный адрес" },
  345. { "end_address", "Конечный адрес" },
  346. { "difficulty", "Сложность" },
  347. { "rank_name", "Звание" },
  348. { "position_name", "Должность" },
  349. { "driver_display", "Водитель" },
  350. { "message", "Сообщение" },
  351. { "inspector_display", "Инспектор" },
  352. { "officer_display", "Сотрудник" },
  353. { "exam_date", "Дата экзамена" },
  354. { "result", "Результат" },
  355. { "issue_date", "Дата выдачи" },
  356. { "category", "Категория" },
  357. { "login", "Логин" },
  358. { "role", "Роль" },
  359. { "is_active", "Активен" },
  360. { "instructor_display", "Инструктор" },
  361. { "driver_full_name", "Водитель" },
  362. { "inspector_full_name", "Инспектор" },
  363. { "attempt_number", "Попытка №" },
  364. { "driver_age", "Возраст водителя" },
  365. { "table_name", "Таблица" },
  366. { "operation", "Операция" },
  367. { "record_id", "ID записи" },
  368. { "changed_at", "Дата и время" },
  369. { "changed_by", "Пользователь" }
  370. };
  371. return captions.ContainsKey(columnName) ? captions[columnName] : columnName;
  372. }
  373. private void BtnRefresh_Click(object sender, EventArgs e)
  374. {
  375. LoadCurrentTable();
  376. }
  377. private void BtnAdd_Click(object sender, EventArgs e)
  378. {
  379. if (!UserSession.IsAdmin)
  380. {
  381. ShowReadonlyMessage();
  382. return;
  383. }
  384. if (currentTable == null)
  385. return;
  386. if (currentTable.IsReadOnly)
  387. {
  388. MessageBox.Show("Этот раздел предназначен только для просмотра.", "Информация",
  389. MessageBoxButtons.OK, MessageBoxIcon.Information);
  390. return;
  391. }
  392. using (RecordEditForm form = new RecordEditForm(DbSettings.ConnectionString, currentTable, null))
  393. {
  394. if (form.ShowDialog(this) == DialogResult.OK)
  395. {
  396. LoadCurrentTable();
  397. LoadExamSearch();
  398. }
  399. }
  400. }
  401. private void BtnEdit_Click(object sender, EventArgs e)
  402. {
  403. if (!UserSession.IsAdmin)
  404. {
  405. ShowReadonlyMessage();
  406. return;
  407. }
  408. if (currentTable != null && currentTable.IsReadOnly)
  409. {
  410. MessageBox.Show("Этот раздел предназначен только для просмотра.", "Информация",
  411. MessageBoxButtons.OK, MessageBoxIcon.Information);
  412. return;
  413. }
  414. if (currentTable == null || dgvTables.SelectedRows.Count == 0)
  415. {
  416. MessageBox.Show("Выберите запись для изменения.", "Информация",
  417. MessageBoxButtons.OK, MessageBoxIcon.Information);
  418. return;
  419. }
  420. DataRowView rowView = dgvTables.SelectedRows[0].DataBoundItem as DataRowView;
  421. if (rowView == null)
  422. return;
  423. using (RecordEditForm form = new RecordEditForm(DbSettings.ConnectionString, currentTable, rowView.Row))
  424. {
  425. if (form.ShowDialog(this) == DialogResult.OK)
  426. {
  427. LoadCurrentTable();
  428. LoadExamSearch();
  429. }
  430. }
  431. }
  432. private void BtnDelete_Click(object sender, EventArgs e)
  433. {
  434. if (!UserSession.IsAdmin)
  435. {
  436. ShowReadonlyMessage();
  437. return;
  438. }
  439. if (currentTable != null && currentTable.IsReadOnly)
  440. {
  441. MessageBox.Show("Этот раздел предназначен только для просмотра.", "Информация",
  442. MessageBoxButtons.OK, MessageBoxIcon.Information);
  443. return;
  444. }
  445. if (currentTable == null || dgvTables.SelectedRows.Count == 0)
  446. {
  447. MessageBox.Show("Выберите запись для удаления.", "Информация",
  448. MessageBoxButtons.OK, MessageBoxIcon.Information);
  449. return;
  450. }
  451. DataRowView rowView = dgvTables.SelectedRows[0].DataBoundItem as DataRowView;
  452. if (rowView == null)
  453. return;
  454. object pkValue = rowView.Row[currentTable.PrimaryKey];
  455. if (currentTable.Name == "app_users" && Convert.ToInt32(pkValue) == UserSession.CurrentUser.UserId)
  456. {
  457. MessageBox.Show("Нельзя удалить текущую учётную запись, под которой выполнен вход.",
  458. "Удаление запрещено", MessageBoxButtons.OK, MessageBoxIcon.Warning);
  459. return;
  460. }
  461. DialogResult result = MessageBox.Show("Удалить выбранную запись?",
  462. "Подтверждение", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
  463. if (result != DialogResult.Yes)
  464. return;
  465. try
  466. {
  467. using (NpgsqlConnection connection = new NpgsqlConnection(DbSettings.ConnectionString))
  468. {
  469. connection.Open();
  470. DbAppContext.Apply(connection);
  471. string sql = "delete from " + FullName(currentTable.Name) + " where " + Quote(currentTable.PrimaryKey) + " = @id";
  472. using (NpgsqlCommand command = new NpgsqlCommand(sql, connection))
  473. {
  474. command.Parameters.AddWithValue("@id", pkValue);
  475. command.ExecuteNonQuery();
  476. DbAppContext.MarkLatestAuditLogin(connection, currentTable.Name, pkValue);
  477. }
  478. }
  479. LoadCurrentTable();
  480. LoadExamSearch();
  481. }
  482. catch (Exception ex)
  483. {
  484. MessageBox.Show(AppError.ToUserMessage(ex, "Не удалось удалить запись."),
  485. "Ошибка удаления", MessageBoxButtons.OK, MessageBoxIcon.Error);
  486. }
  487. }
  488. private void ShowReadonlyMessage()
  489. {
  490. MessageBox.Show("Обычный пользователь работает только с личным кабинетом. Изменение данных доступно только роли admin.",
  491. "Недостаточно прав", MessageBoxButtons.OK, MessageBoxIcon.Information);
  492. }
  493. private void BtnExportTable_Click(object sender, EventArgs e)
  494. {
  495. ExportTable(dgvTables, currentTable == null ? "table" : currentTable.Name);
  496. }
  497. private void BtnSearch_Click(object sender, EventArgs e)
  498. {
  499. LoadExamSearch();
  500. }
  501. private void BtnReset_Click(object sender, EventArgs e)
  502. {
  503. txtDriverFilter.Clear();
  504. txtSchoolFilter.Clear();
  505. cmbResultFilter.SelectedIndex = 0;
  506. dtFrom.Checked = false;
  507. dtTo.Checked = false;
  508. LoadExamSearch();
  509. }
  510. private void BtnExportSearch_Click(object sender, EventArgs e)
  511. {
  512. ExportTable(dgvSearch, "exam_search");
  513. }
  514. private void LoadExamSearch()
  515. {
  516. try
  517. {
  518. using (NpgsqlConnection connection = new NpgsqlConnection(DbSettings.ConnectionString))
  519. {
  520. connection.Open();
  521. const string sql = @"
  522. select exam_id, exam_date, result, driver_full_name, school_name, route_name, inspector_full_name, attempt_number, driver_age
  523. from akulova_vs.v_exam_search
  524. where (cast(@driver as text) = '' or driver_full_name ilike '%' || cast(@driver as text) || '%')
  525. and (cast(@school as text) = '' or school_name ilike '%' || cast(@school as text) || '%')
  526. and (cast(@result as text) = '' or result = cast(@result as text))
  527. and (cast(@date_from as date) is null or exam_date >= cast(@date_from as date))
  528. and (cast(@date_to as date) is null or exam_date <= cast(@date_to as date))
  529. order by exam_date desc, exam_id desc;";
  530. using (NpgsqlCommand command = new NpgsqlCommand(sql, connection))
  531. {
  532. command.Parameters.AddWithValue("@driver", txtDriverFilter == null ? "" : txtDriverFilter.Text.Trim());
  533. command.Parameters.AddWithValue("@school", txtSchoolFilter == null ? "" : txtSchoolFilter.Text.Trim());
  534. string selectedResult = cmbResultFilter == null || cmbResultFilter.SelectedIndex <= 0
  535. ? ""
  536. : cmbResultFilter.SelectedItem.ToString();
  537. command.Parameters.AddWithValue("@result", selectedResult);
  538. Npgsql.NpgsqlParameter dateFromParameter = command.Parameters.Add("@date_from", NpgsqlTypes.NpgsqlDbType.Date);
  539. dateFromParameter.Value = dtFrom != null && dtFrom.Checked ? (object)dtFrom.Value.Date : DBNull.Value;
  540. Npgsql.NpgsqlParameter dateToParameter = command.Parameters.Add("@date_to", NpgsqlTypes.NpgsqlDbType.Date);
  541. dateToParameter.Value = dtTo != null && dtTo.Checked ? (object)dtTo.Value.Date : DBNull.Value;
  542. using (NpgsqlDataAdapter adapter = new NpgsqlDataAdapter(command))
  543. {
  544. currentSearchData = new DataTable();
  545. adapter.Fill(currentSearchData);
  546. if (dgvSearch != null)
  547. {
  548. dgvSearch.DataSource = currentSearchData;
  549. ApplyGridPresentation(dgvSearch);
  550. }
  551. }
  552. }
  553. }
  554. }
  555. catch (Exception ex)
  556. {
  557. if (dgvSearch != null)
  558. {
  559. MessageBox.Show(AppError.ToUserMessage(ex, "Не удалось выполнить поиск по экзаменам."),
  560. "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
  561. }
  562. }
  563. }
  564. private void ExportTable(DataGridView grid, string filePrefix)
  565. {
  566. if (grid == null || grid.DataSource == null || grid.Rows.Count == 0)
  567. {
  568. MessageBox.Show("Нет данных для экспорта.", "Информация",
  569. MessageBoxButtons.OK, MessageBoxIcon.Information);
  570. return;
  571. }
  572. using (SaveFileDialog dialog = new SaveFileDialog())
  573. {
  574. dialog.Filter = "CSV файл (*.csv)|*.csv";
  575. dialog.FileName = filePrefix + "_" + DateTime.Now.ToString("yyyyMMdd_HHmm") + ".csv";
  576. if (dialog.ShowDialog(this) != DialogResult.OK)
  577. return;
  578. try
  579. {
  580. WriteCsv(grid, dialog.FileName);
  581. MessageBox.Show("CSV-файл сохранён:\n" + dialog.FileName,
  582. "Экспорт выполнен", MessageBoxButtons.OK, MessageBoxIcon.Information);
  583. }
  584. catch (Exception ex)
  585. {
  586. MessageBox.Show("Не удалось сохранить CSV.\n\n" + ex.Message,
  587. "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error);
  588. }
  589. }
  590. }
  591. private void WriteCsv(DataGridView grid, string path)
  592. {
  593. List<DataGridViewColumn> visibleColumns = new List<DataGridViewColumn>();
  594. foreach (DataGridViewColumn column in grid.Columns)
  595. {
  596. if (column.Visible)
  597. visibleColumns.Add(column);
  598. }
  599. using (StreamWriter writer = new StreamWriter(path, false, new UTF8Encoding(true)))
  600. {
  601. for (int i = 0; i < visibleColumns.Count; i++)
  602. {
  603. if (i > 0) writer.Write(';');
  604. writer.Write(EscapeCsv(visibleColumns[i].HeaderText));
  605. }
  606. writer.WriteLine();
  607. foreach (DataGridViewRow row in grid.Rows)
  608. {
  609. if (row.IsNewRow)
  610. continue;
  611. for (int i = 0; i < visibleColumns.Count; i++)
  612. {
  613. if (i > 0) writer.Write(';');
  614. object value = row.Cells[visibleColumns[i].Name].Value;
  615. writer.Write(EscapeCsv(value == null || value == DBNull.Value ? "" : value.ToString()));
  616. }
  617. writer.WriteLine();
  618. }
  619. }
  620. }
  621. private string EscapeCsv(string value)
  622. {
  623. if (value == null) return "";
  624. bool needQuotes = value.Contains(";") || value.Contains("\"") || value.Contains("\n") || value.Contains("\r");
  625. value = value.Replace("\"", "\"\"");
  626. return needQuotes ? "\"" + value + "\"" : value;
  627. }
  628. private void BtnLogout_Click(object sender, EventArgs e)
  629. {
  630. UserSession.Logout();
  631. Close();
  632. }
  633. public static string FullName(string identifier)
  634. {
  635. return Quote(DbSchema) + "." + Quote(identifier);
  636. }
  637. public static string Quote(string identifier)
  638. {
  639. return "\"" + identifier.Replace("\"", "\"\"") + "\"";
  640. }
  641. }
  642. public class TableInfo
  643. {
  644. public string Name { get; private set; }
  645. public string DisplayName { get; private set; }
  646. public string PrimaryKey { get; private set; }
  647. public bool IsReadOnly { get; private set; }
  648. public TableInfo(string name, string displayName, string primaryKey)
  649. : this(name, displayName, primaryKey, false)
  650. {
  651. }
  652. public TableInfo(string name, string displayName, string primaryKey, bool isReadOnly)
  653. {
  654. Name = name;
  655. DisplayName = displayName;
  656. PrimaryKey = primaryKey;
  657. IsReadOnly = isReadOnly;
  658. }
  659. public override string ToString()
  660. {
  661. return DisplayName;
  662. }
  663. }
  664. public class ColumnInfo
  665. {
  666. public string Name { get; set; }
  667. public string DataType { get; set; }
  668. public string UdtName { get; set; }
  669. public bool IsNullable { get; set; }
  670. public bool IsIdentity { get; set; }
  671. public bool IsPrimaryKey { get; set; }
  672. }
  673. public class ComboItem
  674. {
  675. public int? Id { get; private set; }
  676. public string Text { get; private set; }
  677. public ComboItem(int? id, string text)
  678. {
  679. Id = id;
  680. Text = text;
  681. }
  682. public override string ToString()
  683. {
  684. return Text;
  685. }
  686. }
  687. public class FieldBinding
  688. {
  689. public ColumnInfo Column { get; set; }
  690. public Control Control { get; set; }
  691. public bool IsPasswordField { get; set; }
  692. }
  693. }