263 lines
8.9 KiB
Dart
263 lines
8.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:provider/provider.dart';
|
|
import '../models/task_log_model.dart';
|
|
import '../models/project_model.dart';
|
|
import '../models/branch_model.dart';
|
|
import '../services/project_service.dart';
|
|
import '../services/task_log_service.dart';
|
|
import '../providers/auth_provider.dart';
|
|
|
|
class LogFormScreen extends StatefulWidget {
|
|
final TaskLogModel? log;
|
|
const LogFormScreen({super.key, this.log});
|
|
|
|
@override
|
|
State<LogFormScreen> createState() => _LogFormScreenState();
|
|
}
|
|
|
|
class _LogFormScreenState extends State<LogFormScreen> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
late DateTime _selectedDate;
|
|
ProjectModel? _selectedProject;
|
|
BranchModel? _selectedBranch;
|
|
String? _selectedFlavor;
|
|
String _status = 'in_progress';
|
|
final _moduleController = TextEditingController();
|
|
final _remarksController = TextEditingController();
|
|
|
|
List<ProjectModel> _projects = [];
|
|
List<BranchModel> _branches = [];
|
|
bool _isLoading = false;
|
|
bool _isInitializing = true;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_selectedDate = widget.log?.date ?? DateTime.now();
|
|
_status = widget.log?.status ?? 'in_progress';
|
|
_moduleController.text = widget.log?.moduleName ?? '';
|
|
_remarksController.text = widget.log?.remarks ?? '';
|
|
_selectedFlavor = widget.log?.flavor;
|
|
_loadProjects();
|
|
}
|
|
|
|
Future<void> _loadProjects() async {
|
|
final stream = context.read<ProjectService>().getProjects();
|
|
final list = await stream.first;
|
|
if (mounted) {
|
|
setState(() {
|
|
_projects = list;
|
|
if (widget.log != null) {
|
|
_selectedProject = _projects.any((p) => p.id == widget.log!.projectId)
|
|
? _projects.firstWhere((p) => p.id == widget.log!.projectId)
|
|
: null;
|
|
if (_selectedProject != null) {
|
|
_loadBranches(_selectedProject!.id);
|
|
}
|
|
}
|
|
_isInitializing = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _loadBranches(String projectId) async {
|
|
final list = await context.read<ProjectService>().getBranchesOnce(projectId);
|
|
if (mounted) {
|
|
setState(() {
|
|
_branches = list;
|
|
if (widget.log != null && _selectedBranch == null) {
|
|
_selectedBranch = _branches.any((b) => b.id == widget.log!.branchId)
|
|
? _branches.firstWhere((b) => b.id == widget.log!.branchId)
|
|
: null;
|
|
} else {
|
|
_selectedBranch = null;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void _submit() async {
|
|
if (!_formKey.currentState!.validate() || _selectedProject == null || _selectedBranch == null) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text("Please select Project and Branch"), backgroundColor: Colors.orange)
|
|
);
|
|
return;
|
|
}
|
|
|
|
setState(() => _isLoading = true);
|
|
final user = context.read<AuthProvider>().user!;
|
|
|
|
final newLog = TaskLogModel(
|
|
id: widget.log?.id ?? '',
|
|
userId: user.uid,
|
|
userName: user.name,
|
|
projectId: _selectedProject!.id,
|
|
projectName: _selectedProject!.name,
|
|
branchId: _selectedBranch!.id,
|
|
branchName: _selectedBranch!.name,
|
|
flavor: _selectedFlavor,
|
|
moduleName: _moduleController.text.trim(),
|
|
status: _status,
|
|
remarks: _remarksController.text.trim(),
|
|
date: _selectedDate,
|
|
createdAt: widget.log?.createdAt ?? DateTime.now(),
|
|
);
|
|
|
|
try {
|
|
if (widget.log == null) {
|
|
await context.read<TaskLogService>().addTaskLog(newLog);
|
|
} else {
|
|
await context.read<TaskLogService>().updateTaskLog(newLog);
|
|
}
|
|
if (mounted) Navigator.pop(context);
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Failed: $e")));
|
|
}
|
|
} finally {
|
|
if (mounted) setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(widget.log == null ? "Add Work Log" : "Edit Work Log"),
|
|
),
|
|
body: _isInitializing
|
|
? const Center(child: CircularProgressIndicator())
|
|
: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
_buildDateField(),
|
|
const SizedBox(height: 16),
|
|
_buildProjectDropdown(),
|
|
if (_selectedProject != null && _selectedProject!.flavors.isNotEmpty) ...[
|
|
const SizedBox(height: 16),
|
|
_buildFlavorDropdown(),
|
|
],
|
|
const SizedBox(height: 16),
|
|
_buildBranchDropdown(),
|
|
const SizedBox(height: 16),
|
|
TextFormField(
|
|
controller: _moduleController,
|
|
decoration: const InputDecoration(
|
|
labelText: "Module/Task Name",
|
|
hintText: "e.g., Login UI, Bug fixing",
|
|
border: OutlineInputBorder(),
|
|
),
|
|
validator: (v) => v!.isEmpty ? "Required" : null,
|
|
),
|
|
const SizedBox(height: 24),
|
|
const Text("Status", style: TextStyle(fontWeight: FontWeight.bold)),
|
|
const SizedBox(height: 8),
|
|
_buildStatusChips(),
|
|
const SizedBox(height: 24),
|
|
TextFormField(
|
|
controller: _remarksController,
|
|
maxLines: 4,
|
|
decoration: const InputDecoration(
|
|
labelText: "Remarks",
|
|
border: OutlineInputBorder(),
|
|
),
|
|
validator: (v) => v!.isEmpty ? "Required" : null,
|
|
),
|
|
const SizedBox(height: 32),
|
|
_isLoading
|
|
? const Center(child: CircularProgressIndicator())
|
|
: ElevatedButton(
|
|
onPressed: _submit,
|
|
child: Text(widget.log == null ? "SAVE LOG" : "UPDATE LOG"),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDateField() {
|
|
return ListTile(
|
|
contentPadding: EdgeInsets.zero,
|
|
title: const Text("Date of Work", style: TextStyle(fontWeight: FontWeight.bold)),
|
|
subtitle: Text(DateFormat('EEEE, dd MMM yyyy').format(_selectedDate)),
|
|
trailing: const Icon(Icons.calendar_month, color: Colors.indigo),
|
|
onTap: () async {
|
|
final date = await showDatePicker(
|
|
context: context,
|
|
initialDate: _selectedDate,
|
|
firstDate: DateTime.now().subtract(const Duration(days: 30)),
|
|
lastDate: DateTime.now(),
|
|
);
|
|
if (date != null) setState(() => _selectedDate = date);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildProjectDropdown() {
|
|
return DropdownButtonFormField<ProjectModel>(
|
|
value: _selectedProject,
|
|
decoration: const InputDecoration(labelText: "Project", border: OutlineInputBorder()),
|
|
items: _projects.map((p) => DropdownMenuItem(value: p, child: Text(p.name))).toList(),
|
|
onChanged: (v) {
|
|
setState(() {
|
|
_selectedProject = v;
|
|
_selectedBranch = null;
|
|
_selectedFlavor = null;
|
|
_branches = [];
|
|
});
|
|
if (v != null) _loadBranches(v.id);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildFlavorDropdown() {
|
|
return DropdownButtonFormField<String>(
|
|
value: _selectedFlavor,
|
|
decoration: const InputDecoration(labelText: "Flavor", border: OutlineInputBorder()),
|
|
items: _selectedProject!.flavors.map((f) => DropdownMenuItem(value: f, child: Text(f))).toList(),
|
|
onChanged: (v) => setState(() => _selectedFlavor = v),
|
|
validator: (v) => v == null ? "Required" : null,
|
|
);
|
|
}
|
|
|
|
Widget _buildBranchDropdown() {
|
|
return DropdownButtonFormField<BranchModel>(
|
|
value: _selectedBranch,
|
|
decoration: const InputDecoration(labelText: "Branch", border: OutlineInputBorder()),
|
|
items: _branches.map((b) => DropdownMenuItem(value: b, child: Text(b.name))).toList(),
|
|
onChanged: (v) => setState(() => _selectedBranch = v),
|
|
hint: const Text("Select Branch"),
|
|
);
|
|
}
|
|
|
|
Widget _buildStatusChips() {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
_statusChip('in_progress', 'IN PROGRESS', Colors.orange),
|
|
_statusChip('done', 'DONE', Colors.green),
|
|
_statusChip('blocked', 'BLOCKED', Colors.red),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _statusChip(String value, String label, Color color) {
|
|
bool selected = _status == value;
|
|
return ChoiceChip(
|
|
label: Text(label, style: TextStyle(color: selected ? Colors.white : color, fontSize: 11)),
|
|
selected: selected,
|
|
selectedColor: color,
|
|
onSelected: (s) {
|
|
if (s) setState(() => _status = value);
|
|
},
|
|
);
|
|
}
|
|
}
|