From 42dfea40487ad5b1d69d61ad960854dc5bfc6d7c Mon Sep 17 00:00:00 2001 From: NekoLaiS Date: Thu, 17 Jul 2025 14:27:37 +0500 Subject: [PATCH] idk --- lib/data/api_instruction_repository.dart | 0 lib/data/instruction_log_repository.dart | 65 +++++ lib/data/worker_repository.dart | 50 ++++ lib/main.dart | 42 ++- lib/models/instruction_log.dart | 55 ++++ lib/models/worker.dart | 33 +++ lib/providers/instruction_log_provider.dart | 69 +++++ lib/providers/worker_provider.dart | 84 ++++++ lib/screens/add_instruction_page.dart | 118 -------- lib/screens/home_page.dart | 57 ++++ .../instruction_log_form_page.dart | 264 ++++++++++++++++++ .../instruction_log_list_page.dart | 154 ++++++++++ .../add_edit_instruction_page.dart | 6 +- .../instruction_detail_page.dart | 4 +- .../instruction_list_page.dart | 2 +- .../worker_page/list_workers_page.dart | 125 +++++++++ lib/screens/worker_page/worker_form_page.dart | 100 +++++++ pubspec.lock | 137 ++++++++- pubspec.yaml | 3 +- 19 files changed, 1232 insertions(+), 136 deletions(-) delete mode 100644 lib/data/api_instruction_repository.dart create mode 100644 lib/data/instruction_log_repository.dart create mode 100644 lib/data/worker_repository.dart create mode 100644 lib/models/instruction_log.dart create mode 100644 lib/models/worker.dart create mode 100644 lib/providers/instruction_log_provider.dart create mode 100644 lib/providers/worker_provider.dart delete mode 100644 lib/screens/add_instruction_page.dart create mode 100644 lib/screens/home_page.dart create mode 100644 lib/screens/instruction_log_page/instruction_log_form_page.dart create mode 100644 lib/screens/instruction_log_page/instruction_log_list_page.dart rename lib/screens/{ => instruction_page}/add_edit_instruction_page.dart (97%) rename lib/screens/{ => instruction_page}/instruction_detail_page.dart (97%) rename lib/screens/{ => instruction_page}/instruction_list_page.dart (98%) create mode 100644 lib/screens/worker_page/list_workers_page.dart create mode 100644 lib/screens/worker_page/worker_form_page.dart diff --git a/lib/data/api_instruction_repository.dart b/lib/data/api_instruction_repository.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/data/instruction_log_repository.dart b/lib/data/instruction_log_repository.dart new file mode 100644 index 0000000..58f4bcf --- /dev/null +++ b/lib/data/instruction_log_repository.dart @@ -0,0 +1,65 @@ +import '../models/instruction_log.dart'; +import 'interface/IRepository.dart'; + +class InstructionLogRepository implements Repository { + final List _logs = [ + InstructionLog( + id: '1', + workerId: '1', + instructionId: '1', + status: InstructionStatus.completed, + notes: 'Работа выполнена качественно' + ), + InstructionLog( + id: '2', + workerId: '2', + instructionId: '2', + status: InstructionStatus.assigned, + notes: 'В процессе выполнения' + ), + InstructionLog( + id: '3', + workerId: '1', + instructionId: '3', + status: InstructionStatus.assigned, + notes: 'Ожидает начала работ' + ) + ]; + + @override + Future> load() async { + return List.of(_logs); + } + + @override + Future add(InstructionLog log) async { + _logs.add(log); + return log; + } + + @override + Future update(InstructionLog log) async { + final index = _logs.indexWhere((l) => l.id == log.id); + if (index == -1) { + throw Exception('Запись журнала не найдена'); + } + _logs[index] = log; + } + + @override + Future delete(String? id) async { + _logs.removeWhere((log) => log.id == id); + } + + Future> getByWorkerId(String workerId) async { + return _logs.where((log) => log.workerId == workerId).toList(); + } + + Future> getByInstructionId(String instructionId) async { + return _logs.where((log) => log.instructionId == instructionId).toList(); + } + + Future> getByStatus(InstructionStatus status) async { + return _logs.where((log) => log.status == status).toList(); + } +} \ No newline at end of file diff --git a/lib/data/worker_repository.dart b/lib/data/worker_repository.dart new file mode 100644 index 0000000..53581a7 --- /dev/null +++ b/lib/data/worker_repository.dart @@ -0,0 +1,50 @@ +import '../models/worker.dart'; +import 'interface/IRepository.dart'; + +class LocalWorkerRepository implements Repository { + final List _workers = [ + Worker( + id: '1', + name: 'Иван Петров', + + ), + Worker( + id: '2', + name: 'Мария Сидорова', + ), + Worker( + id: '3', + name: 'Алексей Козлов', + ), + Worker( + id: '4', + name: 'Елена Волкова', + ) + ]; + + @override + Future> load() async { + return List.of(_workers); + } + + @override + Future add(Worker worker) async { + _workers.add(worker); + return worker; + } + + @override + Future update(Worker worker) async { + var index = _workers.indexWhere((item) => item.id == worker.id); + if (index != -1) { + _workers[index] = worker; + } + } + + @override + Future delete(String? id) async { + if (id == null) + return; + _workers.removeWhere((item) => item.id == id); + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 9a49c71..5985b53 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,27 +3,48 @@ import 'package:flutter/material.dart'; import 'package:instruction_app/data/interface/IRepository.dart'; import 'package:instruction_app/data/organization_repository.dart'; +import 'package:instruction_app/data/worker_repository.dart'; +import 'package:instruction_app/data/instruction_log_repository.dart'; import 'package:instruction_app/models/organization.dart'; +import 'package:instruction_app/models/worker.dart'; +import 'package:instruction_app/models/instruction_log.dart'; import 'package:instruction_app/providers/organization_ptovider.dart'; -import 'package:instruction_app/screens/auth/login_page.dart'; -import 'package:instruction_app/screens/organization_page/list_organization_page.dart'; +import 'package:instruction_app/providers/worker_provider.dart'; +import 'package:instruction_app/providers/instruction_log_provider.dart'; +//import 'package:instruction_app/screens/auth/login_page.dart'; +//import 'package:instruction_app/screens/organization_page/list_organization_page.dart'; +import 'package:instruction_app/screens/home_page.dart'; import 'package:provider/provider.dart'; import 'data/instruction_repository.dart'; import 'providers/instruction_provider.dart'; -import 'screens/instruction_list_page.dart'; +//import 'screens/instruction_list_page.dart'; //import 'data/api_instruction_repository.dart'; void main() { final InstructionRepository repository = LocalInstructionRepository(); //LocalInstructionRepository(); - // final Repository organizationRepository = LocalOrganizationRepository(); + final Repository organizationRepository = LocalOrganizationRepository(); + final Repository workerRepository = LocalWorkerRepository(); + final Repository instructionLogRepository = InstructionLogRepository(); runApp( - // "Предоставляем" наш провайдер всему дереву виджетов - ChangeNotifierProvider( - create: (context) => InstructionProvider(repository), - // create: (context) => OrganizationProvider(organizationRepository), + // "Предоставляем" наши провайдеры всему дереву виджетов + MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => InstructionProvider(repository), + ), + ChangeNotifierProvider( + create: (context) => OrganizationProvider(organizationRepository), + ), + ChangeNotifierProvider( + create: (context) => WorkerProvider(workerRepository), + ), + ChangeNotifierProvider( + create: (context) => InstructionLogProvider(instructionLogRepository), + ), + ], child: const MyApp(), ), ); @@ -40,7 +61,10 @@ class MyApp extends StatelessWidget { colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), - home: const AuthorizationPage(), + // Для разработки используем HomePage напрямую + // В продакшене можно вернуть AuthorizationPage + home: const HomePage(), + // home: const AuthorizationPage(), ); } } \ No newline at end of file diff --git a/lib/models/instruction_log.dart b/lib/models/instruction_log.dart new file mode 100644 index 0000000..9d2c082 --- /dev/null +++ b/lib/models/instruction_log.dart @@ -0,0 +1,55 @@ +enum InstructionStatus { + assigned, + completed, + overdue, + cancelled +} + +extension InstructionStatusExtension on InstructionStatus { + String get displayName { + switch (this) { + case InstructionStatus.assigned: + return 'Назначен'; + case InstructionStatus.completed: + return 'Завершен'; + case InstructionStatus.overdue: + return 'Просрочен'; + case InstructionStatus.cancelled: + return 'Отменен'; + } + } +} + +class InstructionLog { + final String? id; + final String workerId; + final String instructionId; + final InstructionStatus status; + final String? notes; + + InstructionLog({ + this.id, + required this.workerId, + required this.instructionId, + required this.status, + this.notes, + }); + + InstructionLog copyWith({ + String? id, + String? workerId, + String? instructionId, + InstructionStatus? status, + String? notes, + }) { + return InstructionLog( + id: id ?? this.id, + workerId: workerId ?? this.workerId, + instructionId: instructionId ?? this.instructionId, + status: status ?? this.status, + notes: notes ?? this.notes, + ); + } + + +} \ No newline at end of file diff --git a/lib/models/worker.dart b/lib/models/worker.dart new file mode 100644 index 0000000..1202b78 --- /dev/null +++ b/lib/models/worker.dart @@ -0,0 +1,33 @@ +class Worker { + final String? id; + final String name; + + Worker({ + this.id, + required this.name, + }); + + Worker copyWith({ + String? id, + String? name, + }) { + return Worker( + id: id ?? this.id, + name: name ?? this.name, + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + }; + } + + factory Worker.fromJson(Map json) { + return Worker( + id: json['id'], + name: json['name'], + ); + } +} \ No newline at end of file diff --git a/lib/providers/instruction_log_provider.dart b/lib/providers/instruction_log_provider.dart new file mode 100644 index 0000000..763ab60 --- /dev/null +++ b/lib/providers/instruction_log_provider.dart @@ -0,0 +1,69 @@ +import 'package:flutter/foundation.dart'; +import '../models/instruction_log.dart'; +import '../data/interface/IRepository.dart'; + +class InstructionLogProvider extends ChangeNotifier { + final Repository _repository; + List _logs = []; + bool _isLoading = false; + + InstructionLogProvider(this._repository); + + List get logs => _logs; + bool get isLoading => _isLoading; + + Future loadLogs() async { + _isLoading = true; + notifyListeners(); + + try { + _logs = await _repository.load(); + } catch (e) { + debugPrint('Ошибка загрузки журнала инструктажей: $e'); + } finally { + _isLoading = false; + notifyListeners(); + } + } + + Future addLog(InstructionLog log) async { + try { + final newLog = await _repository.add(log); + _logs.add(newLog); + notifyListeners(); + } catch (e) { + debugPrint('Ошибка добавления записи в журнал: $e'); + } + } + + Future updateLog(InstructionLog log) async { + try { + await _repository.update(log); + final index = _logs.indexWhere((l) => l.id == log.id); + if (index != -1) { + _logs[index] = log; + notifyListeners(); + } + } catch (e) { + debugPrint('Ошибка обновления записи в журнале: $e'); + } + } + + Future deleteLog(String? id) async { + try { + await _repository.delete(id); + _logs.removeWhere((log) => log.id == id); + notifyListeners(); + } catch (e) { + debugPrint('Ошибка удаления записи из журнала: $e'); + } + } + + List getLogsByWorker(String workerId) { + return _logs.where((log) => log.workerId == workerId).toList(); + } + + List getLogsByInstruction(String instructionId) { + return _logs.where((log) => log.instructionId == instructionId).toList(); + } +} \ No newline at end of file diff --git a/lib/providers/worker_provider.dart b/lib/providers/worker_provider.dart new file mode 100644 index 0000000..616afd8 --- /dev/null +++ b/lib/providers/worker_provider.dart @@ -0,0 +1,84 @@ +import 'package:flutter/foundation.dart'; +import '../data/interface/IRepository.dart'; +import '../models/worker.dart'; + +class WorkerProvider extends ChangeNotifier { + final Repository _repository; + List _workers = []; + bool _isLoading = false; + String? _error; + + WorkerProvider(this._repository); + + List get workers => _workers; + bool get isLoading => _isLoading; + String? get error => _error; + + Future loadWorkers() async { + _isLoading = true; + _error = null; + notifyListeners(); + + try { + _workers = await _repository.load(); + } catch (e) { + _error = e.toString(); + } finally { + _isLoading = false; + notifyListeners(); + } + } + + Future addWorker(Worker worker) async { + _isLoading = true; + _error = null; + notifyListeners(); + + try { + final newWorker = await _repository.add(worker); + _workers.add(newWorker); + } catch (e) { + _error = e.toString(); + } finally { + _isLoading = false; + notifyListeners(); + } + } + + Future updateWorker(Worker worker) async { + _isLoading = true; + _error = null; + notifyListeners(); + + try { + await _repository.update(worker); + final index = _workers.indexWhere((w) => w.id == worker.id); + if (index != -1) { + _workers[index] = worker; + } + } catch (e) { + _error = e.toString(); + } finally { + _isLoading = false; + notifyListeners(); + } + } + + Future deleteWorker(String? id) async { + if (id == null) return; + + _isLoading = true; + _error = null; + notifyListeners(); + + try { + await _repository.delete(id); + _workers.removeWhere((worker) => worker.id == id); + } catch (e) { + _error = e.toString(); + } finally { + _isLoading = false; + notifyListeners(); + } + } +} \ No newline at end of file diff --git a/lib/screens/add_instruction_page.dart b/lib/screens/add_instruction_page.dart deleted file mode 100644 index a5f03db..0000000 --- a/lib/screens/add_instruction_page.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../providers/instruction_provider.dart'; - -class AddInstructionPage extends StatefulWidget { - const AddInstructionPage({super.key}); - - @override - State createState() => _AddInstructionPageState(); -} - -class _AddInstructionPageState extends State { - final _formKey = GlobalKey(); - final _titleController = TextEditingController(); - final _contentController = TextEditingController(); - final _tagController = TextEditingController(); - - final List _tags = []; - - void _addTag() { - final tag = _tagController.text.trim().toLowerCase(); - if (tag.isNotEmpty && !_tags.contains(tag)) { - setState(() { - _tags.add(tag); - _tagController.clear(); - }); - } - } - - void _removeTag(String tag) { - setState(() { - _tags.remove(tag); - }); - } - - Future _saveInstruction() async { - if (_formKey.currentState!.validate()) { - final provider = Provider.of(context, listen: false); - await provider.addInstruction( - _titleController.text, - _contentController.text, - _tags, - ); - if (mounted) { - Navigator.of(context).pop(); - } - } - } - - @override - void dispose() { - _titleController.dispose(); - _contentController.dispose(); - _tagController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Новый инструктаж'), - actions: [ - IconButton( - icon: const Icon(Icons.save), - onPressed: _saveInstruction, - ) - ], - ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Form( - key: _formKey, - child: ListView( - children: [ - TextFormField( - controller: _titleController, - decoration: const InputDecoration(labelText: 'Название'), - validator: (value) => (value == null || value.isEmpty) ? 'Введите название' : null, - ), - const SizedBox(height: 16), - TextFormField( - controller: _contentController, - decoration: const InputDecoration(labelText: 'Содержание'), - maxLines: 5, - validator: (value) => (value == null || value.isEmpty) ? 'Введите содержание' : null, - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: TextField( - controller: _tagController, - decoration: const InputDecoration(labelText: 'Добавить тег'), - onSubmitted: (_) => _addTag(), - ), - ), - IconButton( - icon: const Icon(Icons.add_circle_outline), - onPressed: _addTag, - ), - ], - ), - const SizedBox(height: 10), - Wrap( - spacing: 8.0, - children: _tags.map((tag) => Chip( - label: Text(tag), - onDeleted: () => _removeTag(tag), - )).toList(), - ), - ], - ), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/screens/home_page.dart b/lib/screens/home_page.dart new file mode 100644 index 0000000..9dbfbc0 --- /dev/null +++ b/lib/screens/home_page.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:instruction_app/screens/instruction_page/instruction_list_page.dart'; +import 'package:instruction_app/screens/worker_page/list_workers_page.dart'; +import 'package:instruction_app/screens/organization_page/list_organization_page.dart'; +import 'package:instruction_app/screens/instruction_log_page/instruction_log_list_page.dart'; + +class HomePage extends StatefulWidget { + const HomePage({Key? key}) : super(key: key); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + int _selectedIndex = 0; + + static final List _pages = [ + const InstructionListPage(), + const ListWorkersPage(), + const OrganizationListPage(), + const InstructionLogListPage() + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: _pages[_selectedIndex], + bottomNavigationBar: BottomNavigationBar( + type: BottomNavigationBarType.fixed, + currentIndex: _selectedIndex, + onTap: (index) { + setState(() { + _selectedIndex = index; + }); + }, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.description), + label: 'Инструктажи', + ), + BottomNavigationBarItem( + icon: Icon(Icons.people), + label: 'Работники', + ), + BottomNavigationBarItem( + icon: Icon(Icons.business), + label: 'Организации', + ), + BottomNavigationBarItem( + icon: Icon(Icons.assignment), + label: 'Журнал', + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/instruction_log_page/instruction_log_form_page.dart b/lib/screens/instruction_log_page/instruction_log_form_page.dart new file mode 100644 index 0000000..d291ca8 --- /dev/null +++ b/lib/screens/instruction_log_page/instruction_log_form_page.dart @@ -0,0 +1,264 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../../models/instruction_log.dart'; +import '../../models/worker.dart'; +import '../../models/instruction.dart'; +import '../../providers/instruction_log_provider.dart'; +import '../../providers/worker_provider.dart'; +import '../../providers/instruction_provider.dart'; + +class InstructionLogFormPage extends StatefulWidget { + final InstructionLog? log; + + const InstructionLogFormPage({super.key, this.log}); + + @override + State createState() => _InstructionLogFormPageState(); +} + +class _InstructionLogFormPageState extends State { + final _formKey = GlobalKey(); + + String? _selectedWorkerId; + String? _selectedInstructionId; + DateTime _assignedDate = DateTime.now(); + InstructionStatus _status = InstructionStatus.assigned; + DateTime? _completedDate; + final _notesController = TextEditingController(); + + bool get _isEditing => widget.log != null; + + @override + void initState() { + super.initState(); + + if (_isEditing) { + final log = widget.log!; + _selectedWorkerId = log.workerId; + _selectedInstructionId = log.instructionId; + _status = log.status; + _notesController.text = log.notes ?? ''; + } + + WidgetsBinding.instance.addPostFrameCallback((_) { + context.read().loadWorkers(); + context.read().loadInstructions(); + }); + } + + @override + void dispose() { + _notesController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(_isEditing ? 'Редактировать запись' : 'Новая запись'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + ), + body: Consumer2( + builder: (context, workerProvider, instructionProvider, child) { + final workers = workerProvider.workers; + final instructions = instructionProvider.instructions; + + return Padding( + padding: const EdgeInsets.all(16.0), + child: Form( + key: _formKey, + child: Column( + children: [ + DropdownButtonFormField( + value: _selectedWorkerId, + decoration: const InputDecoration( + labelText: 'Работник', + border: OutlineInputBorder(), + ), + items: workers.map((worker) { + return DropdownMenuItem( + value: worker.id, + child: Text('${worker.name}'), + ); + }).toList(), + onChanged: (value) { + setState(() { + _selectedWorkerId = value; + }); + }, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Выберите работника'; + } + return null; + }, + ), + const SizedBox(height: 16), + + DropdownButtonFormField( + value: _selectedInstructionId, + decoration: const InputDecoration( + labelText: 'Инструктаж', + border: OutlineInputBorder(), + ), + items: instructions.map((instruction) { + return DropdownMenuItem( + value: instruction.id, + child: Text(instruction.title), + ); + }).toList(), + onChanged: (value) { + setState(() { + _selectedInstructionId = value; + }); + }, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Выберите инструктаж'; + } + return null; + }, + ), + const SizedBox(height: 16), + + ListTile( + title: const Text('Дата назначения'), + subtitle: Text(_formatDate(_assignedDate)), + trailing: const Icon(Icons.calendar_today), + onTap: () => _selectAssignedDate(context), + ), + const SizedBox(height: 16), + + DropdownButtonFormField( + value: _status, + decoration: const InputDecoration( + labelText: 'Статус', + border: OutlineInputBorder(), + ), + items: InstructionStatus.values.map((status) { + return DropdownMenuItem( + value: status, + child: Text(status.displayName), + ); + }).toList(), + onChanged: (value) { + setState(() { + _status = value!; + if (_status != InstructionStatus.completed) { + _completedDate = null; + } + }); + }, + ), + const SizedBox(height: 16), + + if (_status == InstructionStatus.completed) + ListTile( + title: const Text('Дата завершения'), + subtitle: Text(_completedDate != null + ? _formatDate(_completedDate!) + : 'Не выбрана'), + trailing: const Icon(Icons.calendar_today), + onTap: () => _selectCompletedDate(context), + ), + const SizedBox(height: 16), + + TextFormField( + controller: _notesController, + decoration: const InputDecoration( + labelText: 'Примечания', + border: OutlineInputBorder(), + ), + maxLines: 3, + ), + const SizedBox(height: 32), + + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: () => Navigator.pop(context), + child: const Text('Отмена'), + ), + ElevatedButton( + onPressed: _saveLog, + child: Text(_isEditing ? 'Сохранить' : 'Создать'), + ), + ], + ), + ], + ), + ), + ); + }, + ), + ); + } + + String _formatDate(DateTime date) { + return '${date.day.toString().padLeft(2, '0')}.${date.month.toString().padLeft(2, '0')}.${date.year}'; + } + + Future _selectAssignedDate(BuildContext context) async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: _assignedDate, + firstDate: DateTime(2020), + lastDate: DateTime(2030), + ); + if (picked != null && picked != _assignedDate) { + setState(() { + _assignedDate = picked; + }); + } + } + + Future _selectCompletedDate(BuildContext context) async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: _completedDate ?? DateTime.now(), + firstDate: _assignedDate, + lastDate: DateTime(2030), + ); + if (picked != null) { + setState(() { + _completedDate = picked; + }); + } + } + + void _saveLog() { + if (_formKey.currentState!.validate()) { + if (_selectedWorkerId == null || _selectedInstructionId == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Заполните все обязательные поля')), + ); + return; + } + + if (_status == InstructionStatus.completed && _completedDate == null) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Укажите дату завершения')), + ); + return; + } + + final log = InstructionLog( + id: _isEditing ? widget.log!.id : null, + workerId: _selectedWorkerId!, + instructionId: _selectedInstructionId!, + status: _status, + notes: _notesController.text.isEmpty ? null : _notesController.text, + ); + + if (_isEditing) { + context.read().updateLog(log); + } else { + context.read().addLog(log); + } + + Navigator.pop(context); + } + } +} \ No newline at end of file diff --git a/lib/screens/instruction_log_page/instruction_log_list_page.dart b/lib/screens/instruction_log_page/instruction_log_list_page.dart new file mode 100644 index 0000000..779f408 --- /dev/null +++ b/lib/screens/instruction_log_page/instruction_log_list_page.dart @@ -0,0 +1,154 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../../models/instruction_log.dart'; +import '../../models/worker.dart'; +import '../../models/instruction.dart'; +import '../../providers/instruction_log_provider.dart'; +import '../../providers/worker_provider.dart'; +import '../../providers/instruction_provider.dart'; +import 'instruction_log_form_page.dart'; + +class InstructionLogListPage extends StatefulWidget { + const InstructionLogListPage({super.key}); + + @override + State createState() => _InstructionLogListPageState(); +} + +class _InstructionLogListPageState extends State { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + context.read().loadLogs(); + context.read().loadWorkers(); + context.read().loadInstructions(); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Журнал инструктажей'), + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + ), + body: Consumer3( + builder: (context, logProvider, workerProvider, instructionProvider, child) { + if (logProvider.isLoading) { + return const Center(child: CircularProgressIndicator()); + } + + final logs = logProvider.logs; + final workers = workerProvider.workers; + final instructions = instructionProvider.instructions; + + if (logs.isEmpty) { + return const Center( + child: Text('Журнал инструктажей пуст'), + ); + } + + return ListView.builder( + itemCount: logs.length, + itemBuilder: (context, index) { + final log = logs[index]; + final worker = workers.firstWhere( + (w) => w.id == log.workerId, + orElse: () => Worker(id: log.workerId, name: 'Неизвестный работник'), + ); + final instruction = instructions.firstWhere( + (i) => i.id == log.instructionId, + orElse: () => Instruction( + id: log.instructionId, + title: 'Неизвестный инструктаж', + content: '', + tags: [], + createdAt: DateTime.now(), + ), + ); + + return Card( + margin: const EdgeInsets.all(8.0), + child: ListTile( + title: Text(worker.name), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Инструктаж: ${instruction.title}'), + Text('Статус: ${log.status.displayName}'), + ], + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit), + onPressed: () => _editLog(context, log), + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () => _deleteLog(context, log), + ), + ], + ), + ), + ); + }, + ); + }, + ), + floatingActionButton: FloatingActionButton( + onPressed: () => _addLog(context), + child: const Icon(Icons.add), + ), + ); + } + + String _formatDate(DateTime date) { + return '${date.day.toString().padLeft(2, '0')}.${date.month.toString().padLeft(2, '0')}.${date.year}'; + } + + void _addLog(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const InstructionLogFormPage(), + ), + ); + } + + void _editLog(BuildContext context, InstructionLog log) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => InstructionLogFormPage(log: log), + ), + ); + } + + void _deleteLog(BuildContext context, InstructionLog log) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: const Text('Подтверждение удаления'), + content: const Text('Вы уверены, что хотите удалить эту запись из журнала?'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Отмена'), + ), + TextButton( + onPressed: () { + context.read().deleteLog(log.id); + Navigator.of(context).pop(); + }, + child: const Text('Удалить'), + ), + ], + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/screens/add_edit_instruction_page.dart b/lib/screens/instruction_page/add_edit_instruction_page.dart similarity index 97% rename from lib/screens/add_edit_instruction_page.dart rename to lib/screens/instruction_page/add_edit_instruction_page.dart index 26fe43c..b4f5ba4 100644 --- a/lib/screens/add_edit_instruction_page.dart +++ b/lib/screens/instruction_page/add_edit_instruction_page.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import '../models/instruction.dart'; -import '../providers/instruction_provider.dart'; -import '../widgets/tag_selection_dialog.dart'; +import '/models/instruction.dart'; +import '/providers/instruction_provider.dart'; +import '/widgets/tag_selection_dialog.dart'; class AddEditInstructionPage extends StatefulWidget { final Instruction? initialInstruction; diff --git a/lib/screens/instruction_detail_page.dart b/lib/screens/instruction_page/instruction_detail_page.dart similarity index 97% rename from lib/screens/instruction_detail_page.dart rename to lib/screens/instruction_page/instruction_detail_page.dart index 78af960..f53293c 100644 --- a/lib/screens/instruction_detail_page.dart +++ b/lib/screens/instruction_page/instruction_detail_page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import '../models/instruction.dart'; -import '../providers/instruction_provider.dart'; +import '/models/instruction.dart'; +import '/providers/instruction_provider.dart'; import 'add_edit_instruction_page.dart'; class InstructionDetailPage extends StatelessWidget { diff --git a/lib/screens/instruction_list_page.dart b/lib/screens/instruction_page/instruction_list_page.dart similarity index 98% rename from lib/screens/instruction_list_page.dart rename to lib/screens/instruction_page/instruction_list_page.dart index 9ed1ff3..174e477 100644 --- a/lib/screens/instruction_list_page.dart +++ b/lib/screens/instruction_page/instruction_list_page.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import '../providers/instruction_provider.dart'; +import '/providers/instruction_provider.dart'; import 'add_edit_instruction_page.dart'; import 'instruction_detail_page.dart'; diff --git a/lib/screens/worker_page/list_workers_page.dart b/lib/screens/worker_page/list_workers_page.dart new file mode 100644 index 0000000..6d7397e --- /dev/null +++ b/lib/screens/worker_page/list_workers_page.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../../models/worker.dart'; +import '../../providers/worker_provider.dart'; +import 'worker_form_page.dart'; + +class ListWorkersPage extends StatefulWidget { + const ListWorkersPage({Key? key}) : super(key: key); + + @override + State createState() => _ListWorkersPageState(); +} + +class _ListWorkersPageState extends State { + @override + void initState() { + super.initState(); + // Load workers when the page is initialized + Future.microtask(() => + Provider.of(context, listen: false).loadWorkers() + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Список работников'), + ), + body: Consumer( + builder: (context, workerProvider, child) { + if (workerProvider.isLoading) { + return const Center(child: CircularProgressIndicator()); + } + + if (workerProvider.error != null) { + return Center(child: Text('Ошибка: ${workerProvider.error}')); + } + + final workers = workerProvider.workers; + + if (workers.isEmpty) { + return const Center(child: Text('Список работников пуст')); + } + + return ListView.builder( + itemCount: workers.length, + itemBuilder: (context, index) { + final worker = workers[index]; + return ListTile( + title: Text(worker.name), + + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.edit), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => WorkerFormPage(worker: worker), + ), + ); + }, + ), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + _showDeleteConfirmationDialog(context, worker); + }, + ), + ], + ), + onTap: () { + // Show worker details if needed + }, + ); + }, + ); + }, + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const WorkerFormPage(), + ), + ); + }, + child: const Icon(Icons.add), + ), + ); + } + + Future _showDeleteConfirmationDialog(BuildContext context, Worker worker) async { + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext dialogContext) { + return AlertDialog( + title: const Text('Подтверждение'), + content: Text('Вы уверены, что хотите удалить работника "${worker.name}"?'), + actions: [ + TextButton( + child: const Text('Отмена'), + onPressed: () { + Navigator.of(dialogContext).pop(); + }, + ), + TextButton( + child: const Text('Удалить'), + onPressed: () { + Provider.of(context, listen: false) + .deleteWorker(worker.id); + Navigator.of(dialogContext).pop(); + }, + ), + ], + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/screens/worker_page/worker_form_page.dart b/lib/screens/worker_page/worker_form_page.dart new file mode 100644 index 0000000..32001d6 --- /dev/null +++ b/lib/screens/worker_page/worker_form_page.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../../models/worker.dart'; +import '../../providers/worker_provider.dart'; + +class WorkerFormPage extends StatefulWidget { + final Worker? worker; + + const WorkerFormPage({Key? key, this.worker}) : super(key: key); + + @override + State createState() => _WorkerFormPageState(); +} + +class _WorkerFormPageState extends State { + final _formKey = GlobalKey(); + late TextEditingController _nameController; + late TextEditingController _positionController; + late TextEditingController _departmentController; + late TextEditingController _contactInfoController; + + bool get _isEditing => widget.worker != null; + + @override + void initState() { + super.initState(); + _nameController = TextEditingController(text: widget.worker?.name ?? ''); + } + + @override + void dispose() { + _nameController.dispose(); + _positionController.dispose(); + _departmentController.dispose(); + _contactInfoController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(_isEditing ? 'Редактировать работника' : 'Добавить работника'), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Form( + key: _formKey, + child: ListView( + children: [ + TextFormField( + controller: _nameController, + decoration: const InputDecoration( + labelText: 'ФИО', + border: OutlineInputBorder(), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Пожалуйста, введите ФИО'; + } + return null; + }, + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: _saveWorker, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + ), + child: Text( + _isEditing ? 'Сохранить изменения' : 'Добавить работника', + style: const TextStyle(fontSize: 16), + ), + ), + ], + ), + ), + ), + ); + } + + void _saveWorker() { + if (_formKey.currentState!.validate()) { + final workerProvider = Provider.of(context, listen: false); + + final worker = Worker( + id: widget.worker?.id, + name: _nameController.text, + ); + + if (_isEditing) { + workerProvider.updateWorker(worker); + } else { + workerProvider.addWorker(worker); + } + + Navigator.pop(context); + } + } +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 54856d1..9201173 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -33,6 +33,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" fixnum: dependency: transitive description: @@ -54,6 +70,11 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.5" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" http: dependency: "direct main" description: @@ -102,6 +123,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" provider: dependency: "direct main" description: @@ -110,6 +171,62 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" + url: "https://pub.dev" + source: hosted + version: "2.4.10" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sky_engine: dependency: transitive description: flutter @@ -171,6 +288,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" sdks: - dart: ">=3.7.0-0 <4.0.0" - flutter: ">=3.0.0" + dart: ">=3.7.0 <4.0.0" + flutter: ">=3.27.0" diff --git a/pubspec.yaml b/pubspec.yaml index a1321c3..4302c91 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,8 @@ dependencies: http: ^0.13.6 flutter_hooks: ^0.20.5 provider: ^6.1.2 # Используем provider - uuid: ^4.3.3 + uuid: ^4.3.3 + shared_preferences: ^2.5.3 flutter: uses-material-design: true \ No newline at end of file