import 'package:flutter/material.dart'; import '../../theme/m3_motion.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../models/office.dart'; import '../../providers/profile_provider.dart'; import '../../providers/tickets_provider.dart'; import '../../providers/services_provider.dart'; import '../../widgets/app_page_header.dart'; import '../../widgets/app_state_view.dart'; import '../../widgets/mono_text.dart'; import '../../widgets/responsive_body.dart'; import '../../theme/app_surfaces.dart'; import '../../widgets/tasq_adaptive_list.dart'; import '../../utils/snackbar.dart'; class OfficesScreen extends ConsumerStatefulWidget { const OfficesScreen({super.key}); @override ConsumerState createState() => _OfficesScreenState(); } class _OfficesScreenState extends ConsumerState { final TextEditingController _searchController = TextEditingController(); @override void dispose() { _searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final isAdmin = ref.watch(isAdminProvider); final officesAsync = ref.watch(officesProvider); return Stack( children: [ ResponsiveBody( maxWidth: double.infinity, child: !isAdmin ? const Center(child: Text('Admin access required.')) : Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const AppPageHeader( title: 'Office Management', subtitle: 'Create and manage office locations', ), Expanded( child: officesAsync.when( data: (offices) { if (offices.isEmpty) { return const AppEmptyView( icon: Icons.apartment_outlined, title: 'No offices yet', subtitle: 'Create an office to start assigning users and schedules.', ); } final query = _searchController.text.trim().toLowerCase(); final filteredOffices = query.isEmpty ? offices : offices .where( (office) => office.name .toLowerCase() .contains(query) || office.id .toLowerCase() .contains(query), ) .toList(); return TasQAdaptiveList( items: filteredOffices, filterHeader: SizedBox( width: 320, child: TextField( controller: _searchController, onChanged: (_) => setState(() {}), decoration: const InputDecoration( labelText: 'Search name', prefixIcon: Icon(Icons.search), ), ), ), columns: [ TasQColumn( header: 'Office ID', technical: true, cellBuilder: (context, office) => Text(office.id), ), TasQColumn( header: 'Office Name', cellBuilder: (context, office) => Text(office.name), ), ], rowActions: (office) => [ IconButton( tooltip: 'Edit', icon: const Icon(Icons.edit), onPressed: () => _showOfficeDialog( context, ref, office: office, ), ), IconButton( tooltip: 'Delete', icon: const Icon(Icons.delete), onPressed: () => _confirmDelete(context, ref, office), ), ], mobileTileBuilder: (context, office, actions) { return Card( child: ListTile( dense: true, visualDensity: VisualDensity.compact, leading: const Icon(Icons.apartment_outlined), title: Text(office.name), subtitle: MonoText('ID ${office.id}'), trailing: Wrap(spacing: 8, children: actions), ), ); }, onRequestRefresh: () { ref.read(officesQueryProvider.notifier).state = const OfficeQuery(offset: 0, limit: 50); }, onPageChanged: (firstRow) { ref .read(officesQueryProvider.notifier) .update((q) => q.copyWith(offset: firstRow)); }, isLoading: false, ); }, loading: () => const Center(child: CircularProgressIndicator()), error: (error, _) => AppErrorView( error: error, onRetry: () => ref.invalidate(officesProvider), ), ), ), ], ), ), if (isAdmin) Positioned( right: 16, bottom: 16, child: SafeArea( child: M3ExpandedFab( onPressed: () => _showOfficeDialog(context, ref), icon: const Icon(Icons.add), label: const Text('New Office'), ), ), ), ], ); } Future _showOfficeDialog( BuildContext context, WidgetRef ref, { Office? office, }) async { final nameController = TextEditingController(text: office?.name ?? ''); String? selectedServiceId = office?.serviceId; await m3ShowDialog( context: context, builder: (dialogContext) { bool saving = false; return StatefulBuilder( builder: (context, setState) { return AlertDialog( shape: AppSurfaces.of(context).dialogShape, title: Text(office == null ? 'Create Office' : 'Edit Office'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: nameController, decoration: const InputDecoration( labelText: 'Office name', ), enabled: !saving, ), const SizedBox(height: 12), Consumer( builder: (ctx, dialogRef, _) { final servicesAsync = dialogRef.watch( servicesOnceProvider, ); return servicesAsync.when( data: (services) { return DropdownButtonFormField( initialValue: selectedServiceId, decoration: const InputDecoration( labelText: 'Service', ), items: [ const DropdownMenuItem( value: null, child: Text('None'), ), ...services.map( (s) => DropdownMenuItem( value: s.id, child: Text(s.name), ), ), ], onChanged: saving ? null : (v) => setState(() => selectedServiceId = v), ); }, loading: () => const Padding( padding: EdgeInsets.symmetric(vertical: 8.0), child: LinearProgressIndicator(), ), error: (e, _) => Text('Failed to load services: $e'), ); }, ), ], ), ), actions: [ TextButton( onPressed: saving ? null : () => Navigator.of(dialogContext).pop(), child: const Text('Cancel'), ), FilledButton( onPressed: saving ? null : () async { final name = nameController.text.trim(); if (name.isEmpty) { showWarningSnackBar(context, 'Name is required.'); return; } setState(() => saving = true); final controller = ref.read( officesControllerProvider, ); if (office == null) { await controller.createOffice( name: name, serviceId: selectedServiceId, ); } else { await controller.updateOffice( id: office.id, name: name, serviceId: selectedServiceId, ); } ref.invalidate(officesProvider); if (context.mounted) { Navigator.of(dialogContext).pop(); showSuccessSnackBar( context, office == null ? 'Office "$name" has been created successfully.' : 'Office "$name" has been updated successfully.', ); } }, child: saving ? const SizedBox( height: 18, width: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : Text(office == null ? 'Create' : 'Save'), ), ], ); }, ); }, ); } Future _confirmDelete( BuildContext context, WidgetRef ref, Office office, ) async { await m3ShowDialog( context: context, builder: (dialogContext) { return AlertDialog( title: const Text('Delete Office'), content: Text('Delete ${office.name}? This cannot be undone.'), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text('Cancel'), ), FilledButton( onPressed: () async { await ref .read(officesControllerProvider) .deleteOffice(id: office.id); ref.invalidate(officesProvider); if (context.mounted) { Navigator.of(dialogContext).pop(); } }, child: const Text('Delete'), ), ], ); }, ); } }