| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- using System.Data;
- using TravelAgencyWinForms.Data;
- using TravelAgencyWinForms.Models;
- using TravelAgencyWinForms.Utils;
- namespace TravelAgencyWinForms.Forms;
- public sealed class EntityEditDialog : Form
- {
- private readonly EntityConfig _config;
- private readonly bool _isEdit;
- private readonly Dictionary<string, Control> _controls = new();
- private readonly Dictionary<string, FieldConfig> _fields = new();
- public Dictionary<string, object?> Values { get; } = new();
- public bool PasswordWasEntered { get; private set; }
- public EntityEditDialog(EntityConfig config, DataRow? row = null)
- {
- _config = config;
- _isEdit = row != null;
- Text = (_isEdit ? "Изменение" : "Добавление") + " — " + config.Title;
- StartPosition = FormStartPosition.CenterParent;
- Width = 560;
- Height = Math.Min(760, 180 + config.EditableFields.Count() * 46);
- AppTheme.ApplyForm(this, true);
- BuildLayout(row);
- }
- private void BuildLayout(DataRow? row)
- {
- var main = new TableLayoutPanel
- {
- Dock = DockStyle.Fill,
- Padding = new Padding(18),
- ColumnCount = 2,
- RowCount = _config.EditableFields.Count() + 2,
- AutoScroll = true
- };
- main.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 180));
- main.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
- var currentRow = 0;
- foreach (var field in _config.EditableFields)
- {
- _fields[field.Name] = field;
- main.RowStyles.Add(new RowStyle(SizeType.Absolute, 42));
- var label = new Label
- {
- Text = field.Label + (field.Required ? " *" : string.Empty),
- AutoSize = true,
- Anchor = AnchorStyles.Left,
- ForeColor = AppTheme.Text
- };
- var control = CreateControl(field, row);
- control.Anchor = AnchorStyles.Left | AnchorStyles.Right;
- if (_isEdit && (field.ReadOnlyOnEdit || field.IsKey))
- control.Enabled = false;
- main.Controls.Add(label, 0, currentRow);
- main.Controls.Add(control, 1, currentRow);
- _controls[field.Name] = control;
- currentRow++;
- }
- var buttons = new FlowLayoutPanel { FlowDirection = FlowDirection.RightToLeft, Dock = DockStyle.Fill };
- var saveButton = new Button { Text = "Сохранить", Width = 120, Height = 36 };
- var cancelButton = new Button { Text = "Отмена", Width = 120, Height = 36 };
- AppTheme.StylePrimaryButton(saveButton);
- AppTheme.StyleSecondaryButton(cancelButton);
- saveButton.Click += (_, _) => Save();
- cancelButton.Click += (_, _) => DialogResult = DialogResult.Cancel;
- buttons.Controls.Add(saveButton);
- buttons.Controls.Add(cancelButton);
- main.RowStyles.Add(new RowStyle(SizeType.Absolute, 46));
- main.Controls.Add(buttons, 0, currentRow);
- main.SetColumnSpan(buttons, 2);
- Controls.Add(main);
- }
- private Control CreateControl(FieldConfig field, DataRow? row)
- {
- object? value = null;
- if (row != null && row.Table.Columns.Contains(field.Name) && row[field.Name] != DBNull.Value)
- value = row[field.Name];
- switch (field.Kind)
- {
- case FieldKind.Date:
- var datePicker = new DateTimePicker
- {
- Format = DateTimePickerFormat.Short,
- Width = 240,
- ShowCheckBox = !field.Required
- };
- if (value is DateTime dt)
- {
- datePicker.Value = dt;
- datePicker.Checked = true;
- }
- else
- {
- datePicker.Value = DateTime.Today;
- datePicker.Checked = field.Required;
- }
- return datePicker;
- case FieldKind.Boolean:
- var checkBox = new CheckBox { Text = "Да", AutoSize = true };
- checkBox.Checked = value == null || Convert.ToBoolean(value);
- return checkBox;
- case FieldKind.Lookup:
- var comboBox = new ComboBox { DropDownStyle = ComboBoxStyle.DropDownList, Width = 320 };
- AppTheme.StyleComboBox(comboBox);
- LoadLookup(comboBox, field, value);
- return comboBox;
- case FieldKind.Enum:
- var enumBox = new ComboBox { DropDownStyle = ComboBoxStyle.DropDownList, Width = 320 };
- AppTheme.StyleComboBox(enumBox);
- foreach (var option in field.Options)
- enumBox.Items.Add(option);
- if (value != null && enumBox.Items.Contains(Convert.ToString(value)))
- enumBox.SelectedItem = Convert.ToString(value);
- else if (enumBox.Items.Count > 0)
- enumBox.SelectedIndex = 0;
- return enumBox;
- case FieldKind.Password:
- var passwordBox = new TextBox { Width = 320, UseSystemPasswordChar = true, Text = string.Empty };
- AppTheme.StyleTextBox(passwordBox);
- return passwordBox;
- default:
- var textBox = new TextBox { Width = 320, Text = value?.ToString() ?? string.Empty };
- AppTheme.StyleTextBox(textBox);
- return textBox;
- }
- }
- private static void LoadLookup(ComboBox comboBox, FieldConfig field, object? selectedValue)
- {
- if (string.IsNullOrWhiteSpace(field.LookupSql))
- return;
- var source = Db.Query(field.LookupSql);
- var table = new DataTable();
- table.Columns.Add(field.LookupValueColumn, typeof(object));
- table.Columns.Add(field.LookupDisplayColumn, typeof(string));
- if (!field.Required)
- table.Rows.Add(DBNull.Value, "—");
- foreach (DataRow sourceRow in source.Rows)
- table.Rows.Add(sourceRow[field.LookupValueColumn] == DBNull.Value ? DBNull.Value : sourceRow[field.LookupValueColumn], Convert.ToString(sourceRow[field.LookupDisplayColumn]) ?? string.Empty);
- comboBox.DataSource = table;
- comboBox.ValueMember = field.LookupValueColumn;
- comboBox.DisplayMember = field.LookupDisplayColumn;
- if (selectedValue != null && selectedValue != DBNull.Value)
- comboBox.SelectedValue = selectedValue;
- else if (comboBox.Items.Count > 0)
- comboBox.SelectedIndex = 0;
- }
- private void Save()
- {
- Values.Clear();
- PasswordWasEntered = false;
- foreach (var (name, control) in _controls)
- {
- var field = _fields[name];
- object? value;
- try
- {
- value = ReadControlValue(field, control);
- }
- catch (Exception ex)
- {
- MessageBox.Show(ErrorHelper.ToUserMessage(ex), "Проверка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
- return;
- }
- if (field.Required && (value == null || string.IsNullOrWhiteSpace(Convert.ToString(value))))
- {
- MessageBox.Show($"Поле '{field.Label}' должно быть заполнено.", "Проверка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
- return;
- }
- if (field.Kind == FieldKind.Password)
- {
- PasswordWasEntered = !string.IsNullOrWhiteSpace(Convert.ToString(value));
- if (!_isEdit && !PasswordWasEntered)
- {
- MessageBox.Show("Введите пароль.", "Проверка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
- return;
- }
- }
- Values[name] = value;
- }
- if (!ValidateDomainRules())
- return;
- DialogResult = DialogResult.OK;
- Close();
- }
- private object? ReadControlValue(FieldConfig field, Control control)
- {
- return field.Kind switch
- {
- FieldKind.Date => ((DateTimePicker)control).Checked ? ((DateTimePicker)control).Value.Date : null,
- FieldKind.Boolean => ((CheckBox)control).Checked,
- FieldKind.Lookup => ((ComboBox)control).SelectedValue == DBNull.Value ? null : ((ComboBox)control).SelectedValue,
- FieldKind.Enum => ((ComboBox)control).SelectedItem?.ToString(),
- FieldKind.Integer => ParseInteger(field, control.Text),
- FieldKind.Decimal => ParseDecimal(field, control.Text),
- _ => string.IsNullOrWhiteSpace(control.Text.Trim()) ? null : control.Text.Trim()
- };
- }
- private static int? ParseInteger(FieldConfig field, string text)
- {
- var value = text.Trim();
- if (string.IsNullOrWhiteSpace(value))
- return null;
- if (!int.TryParse(value, out var number))
- throw new InvalidOperationException($"Поле '{field.Label}' должно быть целым числом.");
- return number;
- }
- private static decimal? ParseDecimal(FieldConfig field, string text)
- {
- var value = text.Trim().Replace(',', '.');
- if (string.IsNullOrWhiteSpace(value))
- return null;
- if (!decimal.TryParse(value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var number))
- throw new InvalidOperationException($"Поле '{field.Label}' должно быть числом.");
- return number;
- }
- private bool ValidateDomainRules()
- {
- try
- {
- if (_config.TableName == "positions" && Values.TryGetValue("salary", out var salaryObj) && salaryObj != null && Convert.ToDecimal(salaryObj) < 0)
- return Warn("Зарплата не может быть отрицательной.");
- if (_config.TableName == "tours" && Values.TryGetValue("price", out var priceObj) && priceObj != null && Convert.ToDecimal(priceObj) < 0)
- return Warn("Стоимость тура не может быть отрицательной.");
- if (_config.TableName == "employees" && Values.TryGetValue("birth_date", out var birthObj) && birthObj is DateTime birthDate)
- {
- if (birthDate > DateTime.Today)
- return Warn("Дата рождения не может быть в будущем.");
- if (birthDate > DateTime.Today.AddYears(-18))
- return Warn("Сотрудник должен быть старше 18 лет.");
- }
- if (_config.TableName == "tour_list" && Values.TryGetValue("start_date", out var startObj) && Values.TryGetValue("end_date", out var endObj) && startObj is DateTime start && endObj is DateTime end && end < start)
- return Warn("Дата окончания не может быть раньше даты начала.");
- if (_config.TableName == "bookings" && Values.TryGetValue("booking_date", out var bookingObj) && bookingObj is DateTime bookingDate && bookingDate > DateTime.Today)
- return Warn("Дата оформления не может быть в будущем.");
- }
- catch (Exception ex)
- {
- return Warn(ErrorHelper.ToUserMessage(ex));
- }
- return true;
- }
- private static bool Warn(string message)
- {
- MessageBox.Show(message, "Проверка", MessageBoxButtons.OK, MessageBoxIcon.Warning);
- return false;
- }
- }
|