| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- using System.Data;
- using System.Text;
- using Npgsql;
- using TravelAgencyWinForms.Data;
- using TravelAgencyWinForms.Models;
- using TravelAgencyWinForms.Utils;
- namespace TravelAgencyWinForms.Forms;
- public partial class TableEditorForm : Form
- {
- private readonly EntityConfig _config;
- private readonly bool _canEdit;
- private DataTable _sourceTable = new();
- public TableEditorForm(EntityConfig config, bool canEdit)
- {
- _config = config;
- _canEdit = canEdit && !config.ReadOnly;
- InitializeComponent();
- ApplyStyle();
- ConfigureForm();
- Load += (_, _) => LoadData();
- }
- private void ApplyStyle()
- {
- AppTheme.ApplyForm(this);
- AppTheme.StyleGrid(dataGrid);
- AppTheme.StyleTextBox(searchTextBox);
- AppTheme.StylePrimaryButton(refreshButton);
- AppTheme.StyleSecondaryButton(addButton);
- AppTheme.StyleSecondaryButton(editButton);
- AppTheme.StyleDangerButton(deleteButton);
- headerPanel.BackColor = AppTheme.Surface;
- titleLabel.ForeColor = AppTheme.Text;
- subtitleLabel.ForeColor = AppTheme.Muted;
- searchLabel.ForeColor = AppTheme.Muted;
- infoLabel.ForeColor = AppTheme.Muted;
- emptyLabel.ForeColor = AppTheme.Muted;
- var image = AppTheme.LoadImage("empty_results.png");
- if (image != null)
- emptyPictureBox.Image = image;
- }
- private void ConfigureForm()
- {
- Text = _config.Title;
- titleLabel.Text = _config.Title;
- subtitleLabel.Text = _config.ReadOnly ? "Просмотр данных" : "Просмотр и редактирование данных";
- addButton.Enabled = _canEdit;
- editButton.Enabled = _canEdit && _config.Fields.Any(f => !f.IsKey);
- deleteButton.Enabled = _canEdit;
- }
- private void RefreshButton_Click(object? sender, EventArgs e)
- {
- LoadData();
- }
- private void AddButton_Click(object? sender, EventArgs e)
- {
- AddRecord();
- }
- private void EditButton_Click(object? sender, EventArgs e)
- {
- EditRecord();
- }
- private void DeleteButton_Click(object? sender, EventArgs e)
- {
- DeleteRecord();
- }
- private void SearchTextBox_TextChanged(object? sender, EventArgs e)
- {
- ApplySearch();
- }
- private void DataGrid_SelectionChanged(object? sender, EventArgs e)
- {
- UpdateCalculatedPanel();
- }
- private void LoadData()
- {
- Cursor = Cursors.WaitCursor;
- try
- {
- var (sql, parameters) = BuildSelectSql();
- _sourceTable = Db.Query(sql, parameters.ToArray());
- dataGrid.DataSource = _sourceTable;
- ApplyGridPresentation();
- UpdateEmptyState(_sourceTable.Rows.Count == 0);
- UpdateCalculatedPanel();
- infoLabel.Text = $"Записей: {_sourceTable.Rows.Count}";
- }
- catch (Exception ex)
- {
- MessageBox.Show(ErrorHelper.ToUserMessage(ex), "Загрузка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
- }
- finally
- {
- Cursor = Cursors.Default;
- }
- }
- private (string Sql, List<NpgsqlParameter> Parameters) BuildSelectSql()
- {
- var parameters = new List<NpgsqlParameter>();
- string selectPart;
- if (_config.Fields.Count == 0)
- {
- selectPart = "*";
- }
- else
- {
- selectPart = string.Join(", ", _config.GridFields.Select(f => f.Name == "role" && _config.TableName == "app_users" ? "role::text AS role" : Q(f.Name)));
- }
- var sb = new StringBuilder();
- sb.Append("SELECT ").Append(selectPart).Append(" FROM ").Append(Q(_config.SourceName));
- var where = new List<string>();
- if (_config.RestrictByCurrentEmployee && !string.IsNullOrWhiteSpace(_config.RestrictColumn))
- {
- if (CurrentUser.ClientId == null)
- {
- where.Add("1 = 0");
- }
- else
- {
- where.Add(Q(_config.RestrictColumn) + " = @client_id");
- parameters.Add(new NpgsqlParameter("client_id", CurrentUser.ClientId.Value));
- }
- }
- if (where.Count > 0)
- sb.Append(" WHERE ").Append(string.Join(" AND ", where));
- if (!string.IsNullOrWhiteSpace(_config.OrderBy))
- sb.Append(" ORDER BY ").Append(_config.OrderBy);
- return (sb.ToString(), parameters);
- }
- private void ApplyGridPresentation()
- {
- PresentationHelper.ApplyHeaders(dataGrid);
- if (_config.Fields.Count == 0 || _config.ReadOnly)
- PresentationHelper.HideViewColumns(dataGrid);
- foreach (DataGridViewColumn column in dataGrid.Columns)
- if (CalculatedFields.IsCalculated(column.Name))
- column.Visible = false;
- if (dataGrid.Rows.Count > 0 && dataGrid.Columns.Cast<DataGridViewColumn>().Any(c => c.Visible))
- {
- var firstVisible = dataGrid.Columns.Cast<DataGridViewColumn>().First(c => c.Visible);
- dataGrid.CurrentCell = dataGrid.Rows[0].Cells[firstVisible.Index];
- }
- }
- private void UpdateEmptyState(bool isEmpty)
- {
- dataGrid.Visible = !isEmpty;
- emptyPanel.Visible = isEmpty;
- }
- private void UpdateCalculatedPanel()
- {
- calculatedPanel.Controls.Clear();
- var table = GetCurrentGridTable();
- var calculatedColumns = CalculatedFields.GetColumns(table);
- if (calculatedColumns.Count == 0)
- {
- calculatedPanel.Visible = false;
- return;
- }
- calculatedPanel.Visible = true;
- var selectedRow = GetSelectedRow();
- foreach (var columnName in calculatedColumns)
- {
- var value = "—";
- if (selectedRow != null && selectedRow.Table.Columns.Contains(columnName))
- value = CalculatedFields.FormatValue(columnName, selectedRow[columnName]);
- calculatedPanel.Controls.Add(AppTheme.CreateCard(CalculatedFields.GetLabel(columnName), value));
- }
- }
- private DataTable GetCurrentGridTable()
- {
- return dataGrid.DataSource switch
- {
- DataTable table => table,
- DataView view => view.ToTable(),
- _ => _sourceTable
- };
- }
- private void ApplySearch()
- {
- if (_sourceTable.Rows.Count == 0)
- return;
- var search = searchTextBox.Text.Trim().ToLowerInvariant();
- if (string.IsNullOrWhiteSpace(search))
- {
- dataGrid.DataSource = _sourceTable;
- ApplyGridPresentation();
- UpdateEmptyState(_sourceTable.Rows.Count == 0);
- UpdateCalculatedPanel();
- infoLabel.Text = $"Записей: {_sourceTable.Rows.Count}";
- return;
- }
- var filtered = _sourceTable.Clone();
- foreach (DataRow row in _sourceTable.Rows)
- {
- var haystack = string.Join(" ", row.ItemArray.Select(v => v?.ToString() ?? string.Empty)).ToLowerInvariant();
- if (haystack.Contains(search))
- filtered.ImportRow(row);
- }
- dataGrid.DataSource = filtered;
- ApplyGridPresentation();
- UpdateEmptyState(filtered.Rows.Count == 0);
- UpdateCalculatedPanel();
- infoLabel.Text = $"Найдено: {filtered.Rows.Count}";
- }
- private DataRow? GetSelectedRow()
- {
- if (dataGrid.CurrentRow?.DataBoundItem is DataRowView view)
- return view.Row;
- return null;
- }
- private void AddRecord()
- {
- using var dialog = new EntityEditDialog(_config);
- if (dialog.ShowDialog(this) != DialogResult.OK)
- return;
- try
- {
- Db.ResetIdentitySequences();
- var (sql, parameters) = BuildInsertSql(dialog.Values, dialog.PasswordWasEntered);
- Db.SetAppContext(CurrentUser.Login);
- Db.Execute(sql, parameters.ToArray());
- LoadData();
- }
- catch (Exception ex)
- {
- MessageBox.Show(ErrorHelper.ToUserMessage(ex), "Добавление", MessageBoxButtons.OK, MessageBoxIcon.Warning);
- }
- }
- private void EditRecord()
- {
- var row = GetSelectedRow();
- if (row == null)
- {
- MessageBox.Show("Сначала выберите запись.", "Изменение", MessageBoxButtons.OK, MessageBoxIcon.Information);
- return;
- }
- using var dialog = new EntityEditDialog(_config, row);
- if (dialog.ShowDialog(this) != DialogResult.OK)
- return;
- try
- {
- var (sql, parameters) = BuildUpdateSql(dialog.Values, dialog.PasswordWasEntered, row);
- Db.SetAppContext(CurrentUser.Login);
- Db.Execute(sql, parameters.ToArray());
- LoadData();
- }
- catch (Exception ex)
- {
- MessageBox.Show(ErrorHelper.ToUserMessage(ex), "Изменение", MessageBoxButtons.OK, MessageBoxIcon.Warning);
- }
- }
- private void DeleteRecord()
- {
- var row = GetSelectedRow();
- if (row == null)
- {
- MessageBox.Show("Сначала выберите запись.", "Удаление", MessageBoxButtons.OK, MessageBoxIcon.Information);
- return;
- }
- if (MessageBox.Show("Удалить выбранную запись?", "Удаление", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
- return;
- try
- {
- var (sql, parameters) = BuildDeleteSql(row);
- Db.SetAppContext(CurrentUser.Login);
- Db.Execute(sql, parameters.ToArray());
- LoadData();
- }
- catch (Exception ex)
- {
- MessageBox.Show(ErrorHelper.ToUserMessage(ex), "Удаление", MessageBoxButtons.OK, MessageBoxIcon.Warning);
- }
- }
- private (string Sql, List<NpgsqlParameter> Parameters) BuildInsertSql(Dictionary<string, object?> values, bool passwordWasEntered)
- {
- var insertFields = _config.Fields
- .Where(f => !(f.IsKey && f.AutoIdentity))
- .Where(f => f.Kind != FieldKind.Password || passwordWasEntered)
- .ToList();
- var columns = new List<string>();
- var valueSql = new List<string>();
- var parameters = new List<NpgsqlParameter>();
- foreach (var field in insertFields)
- {
- columns.Add(Q(field.Name));
- valueSql.Add(SqlValueExpression(field));
- parameters.Add(MakeParameter(field.Name, values.GetValueOrDefault(field.Name)));
- }
- var sql = $"INSERT INTO {Q(_config.TableName)} ({string.Join(", ", columns)}) VALUES ({string.Join(", ", valueSql)});";
- return (sql, parameters);
- }
- private (string Sql, List<NpgsqlParameter> Parameters) BuildUpdateSql(Dictionary<string, object?> values, bool passwordWasEntered, DataRow originalRow)
- {
- var updateFields = _config.Fields
- .Where(f => !f.IsKey)
- .Where(f => f.Kind != FieldKind.Password || passwordWasEntered)
- .ToList();
- var setParts = new List<string>();
- var parameters = new List<NpgsqlParameter>();
- foreach (var field in updateFields)
- {
- setParts.Add($"{Q(field.Name)} = {SqlValueExpression(field)}");
- parameters.Add(MakeParameter(field.Name, values.GetValueOrDefault(field.Name)));
- }
- var whereParts = new List<string>();
- foreach (var key in _config.KeyFields)
- {
- var paramName = "key_" + key.Name;
- whereParts.Add($"{Q(key.Name)} = @{paramName}");
- parameters.Add(MakeParameter(paramName, originalRow[key.Name]));
- }
- var sql = $"UPDATE {Q(_config.TableName)} SET {string.Join(", ", setParts)} WHERE {string.Join(" AND ", whereParts)};";
- return (sql, parameters);
- }
- private (string Sql, List<NpgsqlParameter> Parameters) BuildDeleteSql(DataRow row)
- {
- var whereParts = new List<string>();
- var parameters = new List<NpgsqlParameter>();
- foreach (var key in _config.KeyFields)
- {
- var paramName = "key_" + key.Name;
- whereParts.Add($"{Q(key.Name)} = @{paramName}");
- parameters.Add(MakeParameter(paramName, row[key.Name]));
- }
- var sql = $"DELETE FROM {Q(_config.TableName)} WHERE {string.Join(" AND ", whereParts)};";
- return (sql, parameters);
- }
- private string SqlValueExpression(FieldConfig field)
- {
- if (_config.TableName == "app_users" && field.Name == "password_hash")
- return "fn_hash_password(@password_hash)";
- if (_config.TableName == "app_users" && field.Name == "role")
- return "@role::app_role";
- return "@" + field.Name;
- }
- private static NpgsqlParameter MakeParameter(string name, object? value)
- {
- return new NpgsqlParameter(name, value ?? DBNull.Value);
- }
- private static string Q(string identifier)
- {
- return "\"" + identifier.Replace("\"", "\"\"") + "\"";
- }
- }
|