import 'package:flutter/material.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/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.')) : officesAsync.when( data: (offices) { if (offices.isEmpty) { return const Center(child: Text('No offices found.')); } 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(); final listBody = 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: () { // For server-side pagination, update the query provider ref.read(officesQueryProvider.notifier).state = const OfficeQuery(offset: 0, limit: 50); }, isLoading: false, ); return Column( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: const EdgeInsets.only(top: 16, bottom: 8), child: Stack( alignment: Alignment.center, children: [ Align( alignment: Alignment.center, child: Text( 'Office Management', textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleLarge ?.copyWith(fontWeight: FontWeight.w700), ), ), ], ), ), Expanded(child: listBody), ], ); }, loading: () => const Center(child: CircularProgressIndicator()), error: (error, _) => Center(child: Text('Failed to load offices: $error')), ), ), if (isAdmin) Positioned( right: 16, bottom: 16, child: SafeArea( child: FloatingActionButton.extended( 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 showDialog( context: context, builder: (dialogContext) { final servicesAsync = ref.watch(servicesOnceProvider); 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), 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 showDialog( 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'), ), ], ); }, ); } }