import 'package:flutter/cupertino.dart'; import 'package:please_pay_me/core/format/formatters.dart'; import 'package:please_pay_me/data/models/job.dart'; import 'package:please_pay_me/features/work/jobs_controller.dart'; import 'package:please_pay_me/theme/theme.dart'; import 'package:please_pay_me/ui/ui.dart'; /// Create / edit form for a job and its payday schedule. class JobFormSheet extends StatefulWidget { const JobFormSheet({super.key, required this.onSubmit, this.initial}); final Job? initial; final Future Function({ required String name, required double salaryAmount, required List payDays, required double firstPayPercent, required WeekendPolicy weekendPolicy, }) onSubmit; @override State createState() => _JobFormSheetState(); } class _JobFormSheetState extends State { late final _nameController = TextEditingController(text: widget.initial?.name ?? ''); late final _salaryController = TextEditingController( text: widget.initial == null ? '' : widget.initial!.salaryAmount.toStringAsFixed(0), ); late int _firstDay = widget.initial?.payDays.firstOrNull ?? 5; late int? _secondDay = (widget.initial?.payDays.length ?? 0) > 1 ? widget.initial!.payDays[1] : 20; late double _firstPercent = widget.initial?.firstPayPercent ?? 40; late WeekendPolicy _policy = widget.initial?.weekendPolicy ?? WeekendPolicy.beforeWeekend; bool _saving = false; String? _error; bool get _isEditing => widget.initial != null; @override void dispose() { _nameController.dispose(); _salaryController.dispose(); super.dispose(); } Future _submit() async { final name = _nameController.text.trim(); final salary = double.tryParse( _salaryController.text.trim().replaceAll(',', '.').replaceAll(' ', ''), ); if (name.isEmpty) { setState(() => _error = 'Введите название работы'); return; } if (salary == null || salary <= 0) { setState(() => _error = 'Введите оклад больше нуля'); return; } setState(() { _saving = true; _error = null; }); final error = await widget.onSubmit( name: name, salaryAmount: salary, payDays: [_firstDay, if (_secondDay != null) _secondDay!], firstPayPercent: _secondDay == null ? 100 : _firstPercent, weekendPolicy: _policy, ); if (!mounted) return; if (error != null) { setState(() { _saving = false; _error = error; }); return; } Navigator.of(context).pop(true); } @override Widget build(BuildContext context) { final salary = double.tryParse( _salaryController.text.trim().replaceAll(',', '.').replaceAll(' ', ''), ) ?? 0; return CupertinoPageScaffold( backgroundColor: AppColors.of(context, AppColors.groupedBackground), navigationBar: AppNavBar( title: _isEditing ? 'Работа' : 'Новая работа', leading: CupertinoButton( padding: EdgeInsets.zero, minimumSize: Size.zero, onPressed: _saving ? null : () => Navigator.of(context).pop(false), child: const AppText.body('Отмена', color: AppColors.accent), ), ), child: SafeArea( child: ListView( padding: const EdgeInsets.only(top: AppSpacing.s4, bottom: AppSpacing.s6), children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: AppSpacing.gutter), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ AppTextField( label: 'Название', placeholder: 'Основная работа', controller: _nameController, enabled: !_saving, ), const SizedBox(height: AppSpacing.s4), AppTextField( label: 'Оклад в месяц', placeholder: '0', controller: _salaryController, enabled: !_saving, keyboardType: const TextInputType.numberWithOptions(decimal: true), onChanged: (_) => setState(() {}), ), ], ), ), const SizedBox(height: AppSpacing.s5), AppListSection( header: 'Дни выплат', footer: 'Если день выпадает на выходные, выплата сдвигается по правилу ниже.', children: [ AppListTile( title: 'Первая выплата', value: '$_firstDay числа', onTap: _saving ? null : () => _pickDay(isFirst: true), ), AppSwitchRow( title: 'Вторая выплата', value: _secondDay != null, onChanged: _saving ? null : (value) => setState(() => _secondDay = value ? 20 : null), ), if (_secondDay != null) AppListTile( title: 'Вторая выплата', value: '$_secondDay числа', onTap: _saving ? null : () => _pickDay(isFirst: false), ), ], ), if (_secondDay != null) ...[ const SizedBox(height: AppSpacing.s5), AppCard( title: 'Доля первой выплаты', subtitle: '${_firstPercent.round()}% — ' '${formatMoney(salary * _firstPercent / 100)} из ${formatMoney(salary)}', child: CupertinoSlider( value: _firstPercent, min: 5, max: 95, divisions: 18, activeColor: AppColors.of(context, AppColors.accent), onChanged: _saving ? null : (v) => setState(() => _firstPercent = v), ), ), ], const SizedBox(height: AppSpacing.s5), const AppSectionHeader('Если выплата на выходных'), AppSegmentedControl( labels: WeekendPolicy.values.map((policy) => policy.label).toList(), index: WeekendPolicy.values.indexOf(_policy), onChanged: (index) => setState(() => _policy = WeekendPolicy.values[index]), ), if (_error != null) Padding( padding: const EdgeInsets.fromLTRB( AppSpacing.gutter, AppSpacing.s3, AppSpacing.gutter, 0, ), child: AppText.footnote(_error!, color: AppColors.systemRed), ), const SizedBox(height: AppSpacing.s5), Padding( padding: const EdgeInsets.symmetric(horizontal: AppSpacing.gutter), child: AppButton( label: _isEditing ? 'Сохранить' : 'Добавить работу', loading: _saving, onPressed: _submit, ), ), ], ), ), ); } Future _pickDay({required bool isFirst}) async { final initial = isFirst ? _firstDay : (_secondDay ?? 20); var picked = initial; final result = await showCupertinoModalPopup( context: context, builder: (ctx) => Container( height: 280, color: AppColors.of(ctx, AppColors.groupedSurface), child: SafeArea( top: false, child: Column( children: [ Expanded( child: CupertinoPicker( itemExtent: 36, scrollController: FixedExtentScrollController(initialItem: initial - 1), onSelectedItemChanged: (index) => picked = index + 1, children: [ for (var day = 1; day <= 31; day++) Center(child: AppText.body('$day числа')), ], ), ), Padding( padding: const EdgeInsets.fromLTRB( AppSpacing.gutter, AppSpacing.s2, AppSpacing.gutter, AppSpacing.s3, ), child: AppButton( label: 'Готово', onPressed: () => Navigator.of(ctx).pop(picked), ), ), ], ), ), ), ); if (result == null || !mounted) return; setState(() { if (isFirst) { _firstDay = result; } else { _secondDay = result; } }); } } Future showJobFormSheet({ required BuildContext context, required JobsController controller, Job? initial, }) async { final saved = await showAppFormSheet( context: context, builder: (_) => JobFormSheet( initial: initial, onSubmit: ({ required name, required salaryAmount, required payDays, required firstPayPercent, required weekendPolicy, }) { if (initial == null) { return controller.create( name: name, salaryAmount: salaryAmount, payDays: payDays, firstPayPercent: firstPayPercent, weekendPolicy: weekendPolicy, ); } return controller.update( jobId: initial.id, name: name, salaryAmount: salaryAmount, payDays: payDays, firstPayPercent: firstPayPercent, weekendPolicy: weekendPolicy, ); }, ), ); if (saved == true && context.mounted) { await showAppToast( context, message: initial == null ? 'Работа добавлена' : 'Работа обновлена', ); } }