idk
This commit is contained in:
parent
c21a43190f
commit
42dfea4048
|
|
@ -0,0 +1,65 @@
|
||||||
|
import '../models/instruction_log.dart';
|
||||||
|
import 'interface/IRepository.dart';
|
||||||
|
|
||||||
|
class InstructionLogRepository implements Repository<InstructionLog> {
|
||||||
|
final List<InstructionLog> _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<List<InstructionLog>> load() async {
|
||||||
|
return List.of(_logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<InstructionLog> add(InstructionLog log) async {
|
||||||
|
_logs.add(log);
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> update(InstructionLog log) async {
|
||||||
|
final index = _logs.indexWhere((l) => l.id == log.id);
|
||||||
|
if (index == -1) {
|
||||||
|
throw Exception('Запись журнала не найдена');
|
||||||
|
}
|
||||||
|
_logs[index] = log;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> delete(String? id) async {
|
||||||
|
_logs.removeWhere((log) => log.id == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<InstructionLog>> getByWorkerId(String workerId) async {
|
||||||
|
return _logs.where((log) => log.workerId == workerId).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<InstructionLog>> getByInstructionId(String instructionId) async {
|
||||||
|
return _logs.where((log) => log.instructionId == instructionId).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<InstructionLog>> getByStatus(InstructionStatus status) async {
|
||||||
|
return _logs.where((log) => log.status == status).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import '../models/worker.dart';
|
||||||
|
import 'interface/IRepository.dart';
|
||||||
|
|
||||||
|
class LocalWorkerRepository implements Repository<Worker> {
|
||||||
|
final List<Worker> _workers = [
|
||||||
|
Worker(
|
||||||
|
id: '1',
|
||||||
|
name: 'Иван Петров',
|
||||||
|
|
||||||
|
),
|
||||||
|
Worker(
|
||||||
|
id: '2',
|
||||||
|
name: 'Мария Сидорова',
|
||||||
|
),
|
||||||
|
Worker(
|
||||||
|
id: '3',
|
||||||
|
name: 'Алексей Козлов',
|
||||||
|
),
|
||||||
|
Worker(
|
||||||
|
id: '4',
|
||||||
|
name: 'Елена Волкова',
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Worker>> load() async {
|
||||||
|
return List.of(_workers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Worker> add(Worker worker) async {
|
||||||
|
_workers.add(worker);
|
||||||
|
return worker;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> update(Worker worker) async {
|
||||||
|
var index = _workers.indexWhere((item) => item.id == worker.id);
|
||||||
|
if (index != -1) {
|
||||||
|
_workers[index] = worker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> delete(String? id) async {
|
||||||
|
if (id == null)
|
||||||
|
return;
|
||||||
|
_workers.removeWhere((item) => item.id == id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,27 +3,48 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:instruction_app/data/interface/IRepository.dart';
|
import 'package:instruction_app/data/interface/IRepository.dart';
|
||||||
import 'package:instruction_app/data/organization_repository.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/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/providers/organization_ptovider.dart';
|
||||||
import 'package:instruction_app/screens/auth/login_page.dart';
|
import 'package:instruction_app/providers/worker_provider.dart';
|
||||||
import 'package:instruction_app/screens/organization_page/list_organization_page.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 'package:provider/provider.dart';
|
||||||
import 'data/instruction_repository.dart';
|
import 'data/instruction_repository.dart';
|
||||||
import 'providers/instruction_provider.dart';
|
import 'providers/instruction_provider.dart';
|
||||||
import 'screens/instruction_list_page.dart';
|
//import 'screens/instruction_list_page.dart';
|
||||||
//import 'data/api_instruction_repository.dart';
|
//import 'data/api_instruction_repository.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
final InstructionRepository repository = LocalInstructionRepository();
|
final InstructionRepository repository = LocalInstructionRepository();
|
||||||
//LocalInstructionRepository();
|
//LocalInstructionRepository();
|
||||||
|
|
||||||
// final Repository<Organization> organizationRepository = LocalOrganizationRepository();
|
final Repository<Organization> organizationRepository = LocalOrganizationRepository();
|
||||||
|
final Repository<Worker> workerRepository = LocalWorkerRepository();
|
||||||
|
final Repository<InstructionLog> instructionLogRepository = InstructionLogRepository();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
// "Предоставляем" наш провайдер всему дереву виджетов
|
// "Предоставляем" наши провайдеры всему дереву виджетов
|
||||||
ChangeNotifierProvider(
|
MultiProvider(
|
||||||
create: (context) => InstructionProvider(repository),
|
providers: [
|
||||||
// create: (context) => OrganizationProvider(organizationRepository),
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => InstructionProvider(repository),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => OrganizationProvider(organizationRepository),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => WorkerProvider(workerRepository),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (context) => InstructionLogProvider(instructionLogRepository),
|
||||||
|
),
|
||||||
|
],
|
||||||
child: const MyApp(),
|
child: const MyApp(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -40,7 +61,10 @@ class MyApp extends StatelessWidget {
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
home: const AuthorizationPage(),
|
// Для разработки используем HomePage напрямую
|
||||||
|
// В продакшене можно вернуть AuthorizationPage
|
||||||
|
home: const HomePage(),
|
||||||
|
// home: const AuthorizationPage(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory Worker.fromJson(Map<String, dynamic> json) {
|
||||||
|
return Worker(
|
||||||
|
id: json['id'],
|
||||||
|
name: json['name'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<InstructionLog> _repository;
|
||||||
|
List<InstructionLog> _logs = [];
|
||||||
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
InstructionLogProvider(this._repository);
|
||||||
|
|
||||||
|
List<InstructionLog> get logs => _logs;
|
||||||
|
bool get isLoading => _isLoading;
|
||||||
|
|
||||||
|
Future<void> loadLogs() async {
|
||||||
|
_isLoading = true;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
try {
|
||||||
|
_logs = await _repository.load();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Ошибка загрузки журнала инструктажей: $e');
|
||||||
|
} finally {
|
||||||
|
_isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addLog(InstructionLog log) async {
|
||||||
|
try {
|
||||||
|
final newLog = await _repository.add(log);
|
||||||
|
_logs.add(newLog);
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Ошибка добавления записи в журнал: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> 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<void> deleteLog(String? id) async {
|
||||||
|
try {
|
||||||
|
await _repository.delete(id);
|
||||||
|
_logs.removeWhere((log) => log.id == id);
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Ошибка удаления записи из журнала: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<InstructionLog> getLogsByWorker(String workerId) {
|
||||||
|
return _logs.where((log) => log.workerId == workerId).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<InstructionLog> getLogsByInstruction(String instructionId) {
|
||||||
|
return _logs.where((log) => log.instructionId == instructionId).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import '../data/interface/IRepository.dart';
|
||||||
|
import '../models/worker.dart';
|
||||||
|
|
||||||
|
class WorkerProvider extends ChangeNotifier {
|
||||||
|
final Repository<Worker> _repository;
|
||||||
|
List<Worker> _workers = [];
|
||||||
|
bool _isLoading = false;
|
||||||
|
String? _error;
|
||||||
|
|
||||||
|
WorkerProvider(this._repository);
|
||||||
|
|
||||||
|
List<Worker> get workers => _workers;
|
||||||
|
bool get isLoading => _isLoading;
|
||||||
|
String? get error => _error;
|
||||||
|
|
||||||
|
Future<void> loadWorkers() async {
|
||||||
|
_isLoading = true;
|
||||||
|
_error = null;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
try {
|
||||||
|
_workers = await _repository.load();
|
||||||
|
} catch (e) {
|
||||||
|
_error = e.toString();
|
||||||
|
} finally {
|
||||||
|
_isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> 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<void> 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<void> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<AddInstructionPage> createState() => _AddInstructionPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AddInstructionPageState extends State<AddInstructionPage> {
|
|
||||||
final _formKey = GlobalKey<FormState>();
|
|
||||||
final _titleController = TextEditingController();
|
|
||||||
final _contentController = TextEditingController();
|
|
||||||
final _tagController = TextEditingController();
|
|
||||||
|
|
||||||
final List<String> _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<void> _saveInstruction() async {
|
|
||||||
if (_formKey.currentState!.validate()) {
|
|
||||||
final provider = Provider.of<InstructionProvider>(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(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<HomePage> createState() => _HomePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomePageState extends State<HomePage> {
|
||||||
|
int _selectedIndex = 0;
|
||||||
|
|
||||||
|
static final List<Widget> _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: 'Журнал',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<InstructionLogFormPage> createState() => _InstructionLogFormPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InstructionLogFormPageState extends State<InstructionLogFormPage> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
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<WorkerProvider>().loadWorkers();
|
||||||
|
context.read<InstructionProvider>().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<WorkerProvider, InstructionProvider>(
|
||||||
|
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<String>(
|
||||||
|
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<String>(
|
||||||
|
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<InstructionStatus>(
|
||||||
|
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<void> _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<void> _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<InstructionLogProvider>().updateLog(log);
|
||||||
|
} else {
|
||||||
|
context.read<InstructionLogProvider>().addLog(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<InstructionLogListPage> createState() => _InstructionLogListPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InstructionLogListPageState extends State<InstructionLogListPage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
context.read<InstructionLogProvider>().loadLogs();
|
||||||
|
context.read<WorkerProvider>().loadWorkers();
|
||||||
|
context.read<InstructionProvider>().loadInstructions();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Журнал инструктажей'),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||||
|
),
|
||||||
|
body: Consumer3<InstructionLogProvider, WorkerProvider, InstructionProvider>(
|
||||||
|
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<InstructionLogProvider>().deleteLog(log.id);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text('Удалить'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../models/instruction.dart';
|
import '/models/instruction.dart';
|
||||||
import '../providers/instruction_provider.dart';
|
import '/providers/instruction_provider.dart';
|
||||||
import '../widgets/tag_selection_dialog.dart';
|
import '/widgets/tag_selection_dialog.dart';
|
||||||
|
|
||||||
class AddEditInstructionPage extends StatefulWidget {
|
class AddEditInstructionPage extends StatefulWidget {
|
||||||
final Instruction? initialInstruction;
|
final Instruction? initialInstruction;
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../models/instruction.dart';
|
import '/models/instruction.dart';
|
||||||
import '../providers/instruction_provider.dart';
|
import '/providers/instruction_provider.dart';
|
||||||
import 'add_edit_instruction_page.dart';
|
import 'add_edit_instruction_page.dart';
|
||||||
|
|
||||||
class InstructionDetailPage extends StatelessWidget {
|
class InstructionDetailPage extends StatelessWidget {
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../providers/instruction_provider.dart';
|
import '/providers/instruction_provider.dart';
|
||||||
import 'add_edit_instruction_page.dart';
|
import 'add_edit_instruction_page.dart';
|
||||||
import 'instruction_detail_page.dart';
|
import 'instruction_detail_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<ListWorkersPage> createState() => _ListWorkersPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ListWorkersPageState extends State<ListWorkersPage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// Load workers when the page is initialized
|
||||||
|
Future.microtask(() =>
|
||||||
|
Provider.of<WorkerProvider>(context, listen: false).loadWorkers()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Список работников'),
|
||||||
|
),
|
||||||
|
body: Consumer<WorkerProvider>(
|
||||||
|
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<void> _showDeleteConfirmationDialog(BuildContext context, Worker worker) async {
|
||||||
|
return showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (BuildContext dialogContext) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Подтверждение'),
|
||||||
|
content: Text('Вы уверены, что хотите удалить работника "${worker.name}"?'),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: const Text('Отмена'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(dialogContext).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: const Text('Удалить'),
|
||||||
|
onPressed: () {
|
||||||
|
Provider.of<WorkerProvider>(context, listen: false)
|
||||||
|
.deleteWorker(worker.id);
|
||||||
|
Navigator.of(dialogContext).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<WorkerFormPage> createState() => _WorkerFormPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WorkerFormPageState extends State<WorkerFormPage> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
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<WorkerProvider>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
137
pubspec.lock
137
pubspec.lock
|
|
@ -33,6 +33,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.6"
|
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:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -54,6 +70,11 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.20.5"
|
version: "0.20.5"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -102,6 +123,46 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
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:
|
provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -110,6 +171,62 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.5"
|
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:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
@ -171,6 +288,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
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:
|
sdks:
|
||||||
dart: ">=3.7.0-0 <4.0.0"
|
dart: ">=3.7.0 <4.0.0"
|
||||||
flutter: ">=3.0.0"
|
flutter: ">=3.27.0"
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ dependencies:
|
||||||
http: ^0.13.6
|
http: ^0.13.6
|
||||||
flutter_hooks: ^0.20.5
|
flutter_hooks: ^0.20.5
|
||||||
provider: ^6.1.2 # Используем provider
|
provider: ^6.1.2 # Используем provider
|
||||||
uuid: ^4.3.3
|
uuid: ^4.3.3
|
||||||
|
shared_preferences: ^2.5.3
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
Loading…
Reference in New Issue