import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../models/office.dart'; import '../../providers/profile_provider.dart'; import '../../providers/tickets_provider.dart'; import '../../widgets/mono_text.dart'; import '../../widgets/responsive_body.dart'; import '../../theme/app_surfaces.dart'; import '../../widgets/tasq_adaptive_list.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), ), ), Align( alignment: Alignment.centerRight, child: TextButton.icon( onPressed: () => context.go('/settings/users'), icon: const Icon(Icons.group), label: const Text('User access'), ), ), ], ), ), 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 ?? ''); await showDialog( context: context, builder: (dialogContext) { return AlertDialog( shape: AppSurfaces.of(context).dialogShape, title: Text(office == null ? 'Create Office' : 'Edit Office'), content: TextField( controller: nameController, decoration: const InputDecoration(labelText: 'Office name'), ), actions: [ TextButton( onPressed: () => Navigator.of(dialogContext).pop(), child: const Text('Cancel'), ), FilledButton( onPressed: () async { final name = nameController.text.trim(); if (name.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Name is required.')), ); return; } final controller = ref.read(officesControllerProvider); if (office == null) { await controller.createOffice(name: name); } else { await controller.updateOffice(id: office.id, name: name); } ref.invalidate(officesProvider); if (context.mounted) { Navigator.of(dialogContext).pop(); } }, child: 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'), ), ], ); }, ); } }