Added Service
This commit is contained in:
parent
6238c701c0
commit
46a84b4d95
|
|
@ -1,10 +1,15 @@
|
||||||
class Office {
|
class Office {
|
||||||
Office({required this.id, required this.name});
|
Office({required this.id, required this.name, this.serviceId});
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
final String name;
|
final String name;
|
||||||
|
final String? serviceId;
|
||||||
|
|
||||||
factory Office.fromMap(Map<String, dynamic> map) {
|
factory Office.fromMap(Map<String, dynamic> map) {
|
||||||
return Office(id: map['id'] as String, name: map['name'] as String? ?? '');
|
return Office(
|
||||||
|
id: map['id'] as String,
|
||||||
|
name: map['name'] as String? ?? '',
|
||||||
|
serviceId: map['service_id'] as String?,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
lib/models/service.dart
Normal file
10
lib/models/service.dart
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
class Service {
|
||||||
|
Service({required this.id, required this.name});
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
factory Service.fromMap(Map<String, dynamic> map) {
|
||||||
|
return Service(id: map['id'] as String, name: map['name'] as String? ?? '');
|
||||||
|
}
|
||||||
|
}
|
||||||
21
lib/providers/services_provider.dart
Normal file
21
lib/providers/services_provider.dart
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../models/service.dart';
|
||||||
|
import 'supabase_provider.dart';
|
||||||
|
|
||||||
|
final servicesProvider = StreamProvider<List<Service>>((ref) {
|
||||||
|
final client = ref.watch(supabaseClientProvider);
|
||||||
|
return client
|
||||||
|
.from('services')
|
||||||
|
.stream(primaryKey: ['id'])
|
||||||
|
.order('name')
|
||||||
|
.map((rows) => rows.map((r) => Service.fromMap(r)).toList());
|
||||||
|
});
|
||||||
|
|
||||||
|
final servicesOnceProvider = FutureProvider<List<Service>>((ref) async {
|
||||||
|
final client = ref.watch(supabaseClientProvider);
|
||||||
|
final rows = await client.from('services').select().order('name');
|
||||||
|
return (rows as List<dynamic>)
|
||||||
|
.map((r) => Service.fromMap(r as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
});
|
||||||
|
|
@ -377,12 +377,20 @@ class OfficesController {
|
||||||
|
|
||||||
final SupabaseClient _client;
|
final SupabaseClient _client;
|
||||||
|
|
||||||
Future<void> createOffice({required String name}) async {
|
Future<void> createOffice({required String name, String? serviceId}) async {
|
||||||
await _client.from('offices').insert({'name': name});
|
final payload = {'name': name};
|
||||||
|
if (serviceId != null) payload['service_id'] = serviceId;
|
||||||
|
await _client.from('offices').insert(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateOffice({required String id, required String name}) async {
|
Future<void> updateOffice({
|
||||||
await _client.from('offices').update({'name': name}).eq('id', id);
|
required String id,
|
||||||
|
required String name,
|
||||||
|
String? serviceId,
|
||||||
|
}) async {
|
||||||
|
final payload = {'name': name};
|
||||||
|
if (serviceId != null) payload['service_id'] = serviceId;
|
||||||
|
await _client.from('offices').update(payload).eq('id', id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteOffice({required String id}) async {
|
Future<void> deleteOffice({required String id}) async {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import '../../models/office.dart';
|
import '../../models/office.dart';
|
||||||
import '../../providers/profile_provider.dart';
|
import '../../providers/profile_provider.dart';
|
||||||
import '../../providers/tickets_provider.dart';
|
import '../../providers/tickets_provider.dart';
|
||||||
|
import '../../providers/services_provider.dart';
|
||||||
import '../../widgets/mono_text.dart';
|
import '../../widgets/mono_text.dart';
|
||||||
import '../../widgets/responsive_body.dart';
|
import '../../widgets/responsive_body.dart';
|
||||||
import '../../theme/app_surfaces.dart';
|
import '../../theme/app_surfaces.dart';
|
||||||
|
|
@ -163,16 +164,59 @@ class _OfficesScreenState extends ConsumerState<OfficesScreen> {
|
||||||
Office? office,
|
Office? office,
|
||||||
}) async {
|
}) async {
|
||||||
final nameController = TextEditingController(text: office?.name ?? '');
|
final nameController = TextEditingController(text: office?.name ?? '');
|
||||||
|
String? selectedServiceId = office?.serviceId;
|
||||||
|
|
||||||
await showDialog<void>(
|
await showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) {
|
builder: (dialogContext) {
|
||||||
|
final servicesAsync = ref.watch(servicesOnceProvider);
|
||||||
|
return StatefulBuilder(
|
||||||
|
builder: (context, setState) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
shape: AppSurfaces.of(context).dialogShape,
|
shape: AppSurfaces.of(context).dialogShape,
|
||||||
title: Text(office == null ? 'Create Office' : 'Edit Office'),
|
title: Text(office == null ? 'Create Office' : 'Edit Office'),
|
||||||
content: TextField(
|
content: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
controller: nameController,
|
controller: nameController,
|
||||||
decoration: const InputDecoration(labelText: 'Office name'),
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Office name',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
servicesAsync.when(
|
||||||
|
data: (services) {
|
||||||
|
return DropdownButtonFormField<String?>(
|
||||||
|
initialValue: selectedServiceId,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Service',
|
||||||
|
),
|
||||||
|
items: [
|
||||||
|
const DropdownMenuItem<String?>(
|
||||||
|
value: null,
|
||||||
|
child: Text('None'),
|
||||||
|
),
|
||||||
|
...services.map(
|
||||||
|
(s) => DropdownMenuItem<String?>(
|
||||||
|
value: s.id,
|
||||||
|
child: Text(s.name),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (v) =>
|
||||||
|
setState(() => selectedServiceId = v),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: LinearProgressIndicator(),
|
||||||
|
),
|
||||||
|
error: (e, _) => Text('Failed to load services: $e'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
|
|
@ -190,9 +234,16 @@ class _OfficesScreenState extends ConsumerState<OfficesScreen> {
|
||||||
}
|
}
|
||||||
final controller = ref.read(officesControllerProvider);
|
final controller = ref.read(officesControllerProvider);
|
||||||
if (office == null) {
|
if (office == null) {
|
||||||
await controller.createOffice(name: name);
|
await controller.createOffice(
|
||||||
|
name: name,
|
||||||
|
serviceId: selectedServiceId,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await controller.updateOffice(id: office.id, name: name);
|
await controller.updateOffice(
|
||||||
|
id: office.id,
|
||||||
|
name: name,
|
||||||
|
serviceId: selectedServiceId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
ref.invalidate(officesProvider);
|
ref.invalidate(officesProvider);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
|
@ -205,6 +256,8 @@ class _OfficesScreenState extends ConsumerState<OfficesScreen> {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _confirmDelete(
|
Future<void> _confirmDelete(
|
||||||
|
|
|
||||||
30
supabase/migrations/20260221150000_add_services_table.sql
Normal file
30
supabase/migrations/20260221150000_add_services_table.sql
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
-- Add services table and link offices to services
|
||||||
|
|
||||||
|
-- 1) Create services table
|
||||||
|
CREATE TABLE IF NOT EXISTS services (
|
||||||
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name text NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 2) Add service_id to offices
|
||||||
|
ALTER TABLE IF EXISTS offices
|
||||||
|
ADD COLUMN IF NOT EXISTS service_id uuid REFERENCES services(id) ON DELETE SET NULL;
|
||||||
|
|
||||||
|
-- 3) Insert default services
|
||||||
|
INSERT INTO services (name) VALUES
|
||||||
|
('Medical Center Chief'),
|
||||||
|
('Medical Service'),
|
||||||
|
('Nursing Service'),
|
||||||
|
('Hospital Operations and Patient Support Service'),
|
||||||
|
('Finance Service'),
|
||||||
|
('Allied/Ancillary')
|
||||||
|
ON CONFLICT (name) DO NOTHING;
|
||||||
|
|
||||||
|
-- 4) Make existing offices default to Medical Center Chief (for now)
|
||||||
|
UPDATE offices
|
||||||
|
SET service_id = s.id
|
||||||
|
FROM services s
|
||||||
|
WHERE s.name = 'Medical Center Chief';
|
||||||
|
|
||||||
|
-- 5) (Optional) Add index for faster lookups
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_offices_service_id ON offices(service_id);
|
||||||
|
|
@ -68,6 +68,7 @@ void main() {
|
||||||
final task = Task(
|
final task = Task(
|
||||||
id: 'TSK-1',
|
id: 'TSK-1',
|
||||||
ticketId: 'TCK-1',
|
ticketId: 'TCK-1',
|
||||||
|
taskNumber: '2026-02-00001',
|
||||||
title: 'Reboot printer',
|
title: 'Reboot printer',
|
||||||
description: 'Clear queue and reboot',
|
description: 'Clear queue and reboot',
|
||||||
officeId: 'office-1',
|
officeId: 'office-1',
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@ void main() {
|
||||||
final emptyTask = Task(
|
final emptyTask = Task(
|
||||||
id: 'tsk-1',
|
id: 'tsk-1',
|
||||||
ticketId: null,
|
ticketId: null,
|
||||||
|
taskNumber: '2026-02-00002',
|
||||||
title: 'No metadata',
|
title: 'No metadata',
|
||||||
description: '',
|
description: '',
|
||||||
officeId: 'office-1',
|
officeId: 'office-1',
|
||||||
|
|
@ -159,6 +160,7 @@ void main() {
|
||||||
final task = Task(
|
final task = Task(
|
||||||
id: 'tsk-1',
|
id: 'tsk-1',
|
||||||
ticketId: null,
|
ticketId: null,
|
||||||
|
taskNumber: '2026-02-00002',
|
||||||
title: 'Has metadata',
|
title: 'Has metadata',
|
||||||
description: '',
|
description: '',
|
||||||
officeId: 'office-1',
|
officeId: 'office-1',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user