This commit is contained in:
NekoLaiS 2025-07-17 14:27:37 +05:00
parent c21a43190f
commit 42dfea4048
19 changed files with 1232 additions and 136 deletions

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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<Organization> organizationRepository = LocalOrganizationRepository();
final Repository<Organization> organizationRepository = LocalOrganizationRepository();
final Repository<Worker> workerRepository = LocalWorkerRepository();
final Repository<InstructionLog> 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(),
);
}
}

View File

@ -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,
);
}
}

33
lib/models/worker.dart Normal file
View File

@ -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'],
);
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}

View File

@ -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(),
),
],
),
),
),
);
}
}

View File

@ -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: 'Журнал',
),
],
),
);
}
}

View File

@ -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);
}
}
}

View File

@ -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('Удалить'),
),
],
);
},
);
}
}

View File

@ -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;

View File

@ -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 {

View File

@ -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';

View File

@ -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();
},
),
],
);
},
);
}
}

View File

@ -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);
}
}
}

View File

@ -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"

View File

@ -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