import 'dart:math'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; import '../../../core/api/pocketbase_client.dart'; import '../../../core/providers/auth_provider.dart'; import '../../../core/theme/app_theme.dart'; import '../../../models/clinic_discount.dart'; import '../../../models/job.dart'; import '../../../models/patient.dart'; import '../../../models/prosthetic_product.dart'; import '../../lab/discounts/discount_repository.dart'; import '../../lab/products/lab_products_repository.dart'; import 'clinic_jobs_repository.dart'; import '../patients/clinic_patients_repository.dart'; String _mimeFromExt(String ext) => switch (ext) { 'jpg' || 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'webp' => 'image/webp', 'pdf' => 'application/pdf', 'stl' => 'model/stl', 'obj' => 'model/obj', 'ply' => 'model/ply', 'zip' => 'application/zip', 'mp3' => 'audio/mpeg', 'mp4' => 'video/mp4', 'opus' => 'audio/opus', _ => 'application/octet-stream', }; class NewJobScreen extends ConsumerStatefulWidget { const NewJobScreen({super.key}); @override ConsumerState createState() => _NewJobScreenState(); } class _NewJobScreenState extends ConsumerState { final _formKey = GlobalKey(); // Form fields Map? _selectedLab; Patient? _selectedPatient; final _patientCodeController = TextEditingController(); ProstheticType? _selectedProstheticType; final Set _selectedTeeth = {}; final _colorController = TextEditingController(); final _descriptionController = TextEditingController(); DateTime? _dueDate; bool _provaRequired = true; // State List> _labs = []; bool _labsLoading = true; bool _isSubmitting = false; String? _labsError; // File upload final List _pendingFiles = []; // Patient search bool _showPatientSearch = false; final _patientSearchController = TextEditingController(); List _patientResults = []; bool _patientSearchLoading = false; // Price preview ProstheticProduct? _labProduct; double? _effectivePrice; bool _priceLoading = false; @override void initState() { super.initState(); _loadLabs(); } @override void dispose() { _patientCodeController.dispose(); _colorController.dispose(); _descriptionController.dispose(); _patientSearchController.dispose(); super.dispose(); } Future _loadLabs() async { setState(() { _labsLoading = true; _labsError = null; }); try { final tenantId = ref.read(authProvider).activeTenant!.tenant.id; final labs = await ClinicJobsRepository.instance.listApprovedLabs(tenantId); setState(() { _labs = labs; _labsLoading = false; }); } catch (e) { setState(() { _labsError = e.toString(); _labsLoading = false; }); } } Future _fetchPrice() async { if (_selectedLab == null || _selectedProstheticType == null) { setState(() { _labProduct = null; _effectivePrice = null; }); return; } final labId = _selectedLab!['id'] as String; final ptValue = _selectedProstheticType!.value; final clinicTenantId = ref.read(authProvider).activeTenant!.tenant.id; setState(() => _priceLoading = true); try { final products = await LabProductsRepository.instance.listProducts(labId, isActive: true); ProstheticProduct? product; try { product = products.firstWhere((p) => p.prostheticType == ptValue); } catch (_) { product = null; } if (product == null || product.unitPrice == null) { setState(() { _labProduct = null; _effectivePrice = null; _priceLoading = false; }); return; } final discounts = await DiscountRepository.instance.listDiscounts(labId); final applicable = discounts.where((d) => d.isActive && (d.appliesToAll || d.clinicTenantId == clinicTenantId) && (d.appliesToAllTypes || d.prostheticType == ptValue) ).toList(); double price = product.unitPrice!; for (final d in applicable) { price = d.discountType == DiscountType.percentage ? price * (1 - d.discountValue / 100) : price - d.discountValue; } setState(() { _labProduct = product; _effectivePrice = price.clamp(0, double.infinity); _priceLoading = false; }); } catch (_) { setState(() { _labProduct = null; _effectivePrice = null; _priceLoading = false; }); } } Future _searchPatients(String query) async { if (query.trim().isEmpty) { setState(() => _patientResults = []); return; } setState(() => _patientSearchLoading = true); try { final tenantId = ref.read(authProvider).activeTenant!.tenant.id; final results = await ClinicPatientsRepository.instance .listPatients(tenantId, search: query, limit: 10); setState(() { _patientResults = results; _patientSearchLoading = false; }); } catch (_) { setState(() => _patientSearchLoading = false); } } Future _pickDueDate() async { final pickedDate = await showDatePicker( context: context, initialDate: DateTime.now().add(const Duration(days: 7)), firstDate: DateTime.now(), lastDate: DateTime.now().add(const Duration(days: 365)), ); if (pickedDate == null || !mounted) return; final pickedTime = await showTimePicker( context: context, initialTime: const TimeOfDay(hour: 17, minute: 0), ); if (!mounted) return; setState(() { _dueDate = DateTime( pickedDate.year, pickedDate.month, pickedDate.day, pickedTime?.hour ?? 17, pickedTime?.minute ?? 0, ); }); } String _generateProtocolNo() { final now = DateTime.now(); final date = '${now.year}${now.month.toString().padLeft(2, '0')}${now.day.toString().padLeft(2, '0')}'; const chars = '0123456789ABCDEFGHJKLMNPQRSTUVWXYZ'; final rand = List.generate(4, (_) => chars[Random().nextInt(chars.length)]).join(); return 'PR-$date-$rand'; } Future _submit() async { if (!_formKey.currentState!.validate()) return; if (_selectedLab == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Lütfen bir laboratuvar seçin.')), ); return; } if (_selectedProstheticType == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Lütfen protez türünü seçin.')), ); return; } if (_selectedTeeth.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('En az bir diş seçmelisiniz.')), ); return; } setState(() => _isSubmitting = true); try { final auth = ref.read(authProvider); final tenantId = auth.activeTenant!.tenant.id; final rawCode = _patientCodeController.text.trim(); final protocolNo = rawCode.isNotEmpty ? rawCode : _generateProtocolNo(); final job = await ClinicJobsRepository.instance.createJob( clinicTenantId: tenantId, labTenantId: _selectedLab!['id'] as String, patientCode: protocolNo, prostheticId: '', prostheticType: _selectedProstheticType!, teeth: _selectedTeeth.map((t) => t.toString()).toList()..sort(), patientId: _selectedPatient?.id, color: _colorController.text.trim().isNotEmpty ? _colorController.text.trim() : null, description: _descriptionController.text.trim().isNotEmpty ? _descriptionController.text.trim() : null, dueDate: _dueDate?.toIso8601String(), provaRequired: _provaRequired, ); // Upload pending files if (_pendingFiles.isNotEmpty) { final pb = PocketBaseClient.instance.pb; final token = pb.authStore.token; final uploaderId = (pb.authStore.record?.id) ?? (auth.profile?.id ?? ''); for (final file in _pendingFiles) { final bytes = file.bytes; if (bytes == null) continue; final ext = (file.extension ?? '').toLowerCase(); final kind = (ext == 'stl' || ext == 'obj' || ext == 'ply') ? 'scan' : (ext == 'pdf') ? 'document' : 'image'; final mimeType = _mimeFromExt(ext); final req = http.MultipartRequest( 'POST', Uri.parse('https://pocket.kovaksoft.com/api/collections/job_files/records'), ) ..headers['Authorization'] = 'Bearer $token' ..fields['job_id'] = job.id ..fields['clinic_tenant_id'] = job.clinicTenantId ..fields['lab_tenant_id'] = job.labTenantId ..fields['uploaded_by'] = uploaderId ..fields['kind'] = kind ..fields['name'] = file.name ..fields['size'] = bytes.length.toString() ..fields['mime_type'] = mimeType ..files.add(http.MultipartFile.fromBytes( 'file', bytes, filename: file.name, )); final response = await req.send(); if (response.statusCode < 200 || response.statusCode >= 300) { final body = await response.stream.bytesToString(); debugPrint('File upload failed: ${response.statusCode} $body'); } } } if (mounted) { context.go('/clinic/jobs/${job.id}'); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Hata: $e')), ); } } finally { if (mounted) setState(() => _isSubmitting = false); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Yeni İş')), body: Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(16), children: [ // Lab selection _SectionLabel(label: 'Laboratuvar *'), if (_labsLoading) const Center(child: CircularProgressIndicator()) else if (_labsError != null) Row( children: [ Text('Hata: $_labsError', style: const TextStyle(color: AppColors.cancelled)), TextButton( onPressed: _loadLabs, child: const Text('Tekrar Dene'), ), ], ) else DropdownButtonFormField>( initialValue: _selectedLab, decoration: const InputDecoration( hintText: 'Laboratuvar seçin', ), items: _labs .map( (lab) => DropdownMenuItem( value: lab, child: Text(lab['company_name'] as String? ?? ''), ), ) .toList(), onChanged: (val) { setState(() => _selectedLab = val); _fetchPrice(); }, validator: (val) => val == null ? 'Laboratuvar seçimi zorunludur' : null, ), const SizedBox(height: 16), // Protocol number _SectionLabel(label: 'Protokol No (İsteğe Bağlı)'), Row( children: [ Expanded( child: TextFormField( controller: _patientCodeController, decoration: InputDecoration( hintText: 'Boş bırakılırsa otomatik üretilir', suffixIcon: _selectedPatient != null ? const Icon(Icons.person, color: AppColors.success) : null, ), ), ), const SizedBox(width: 8), OutlinedButton.icon( onPressed: () { setState(() => _showPatientSearch = !_showPatientSearch); }, icon: const Icon(Icons.search), label: const Text('Ara'), ), ], ), // Patient search panel if (_showPatientSearch) ...[ const SizedBox(height: 8), TextField( controller: _patientSearchController, decoration: const InputDecoration( hintText: 'Ad, soyad veya kod ile arayın...', prefixIcon: Icon(Icons.search), ), onChanged: _searchPatients, ), if (_patientSearchLoading) const Padding( padding: EdgeInsets.all(8), child: Center(child: CircularProgressIndicator()), ), ..._patientResults.map( (p) => ListTile( dense: true, leading: const Icon(Icons.person_outline), title: Text(p.displayName), subtitle: Text(p.patientCode), onTap: () { setState(() { _selectedPatient = p; _patientCodeController.text = p.patientCode; _showPatientSearch = false; _patientSearchController.clear(); _patientResults.clear(); }); }, ), ), ], const SizedBox(height: 16), // Prosthetic type _SectionLabel(label: 'Protez Türü *'), DropdownButtonFormField( initialValue: _selectedProstheticType, decoration: const InputDecoration( hintText: 'Protez türü seçin', ), items: ProstheticType.values .map( (pt) => DropdownMenuItem( value: pt, child: Text(pt.label), ), ) .toList(), onChanged: (val) { setState(() => _selectedProstheticType = val); _fetchPrice(); }, validator: (val) => val == null ? 'Protez türü zorunludur' : null, ), // Price preview if (_priceLoading) const Padding( padding: EdgeInsets.only(top: 8), child: Row(children: [ SizedBox(width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 1.5)), SizedBox(width: 8), Text('Fiyat yükleniyor...', style: TextStyle(fontSize: 12, color: AppColors.textMuted)), ]), ) else if (_labProduct != null && _effectivePrice != null) ...[ const SizedBox(height: 8), _PricePreviewChip( product: _labProduct!, effectivePrice: _effectivePrice!, ), ], const SizedBox(height: 12), // Prova flag _ProvaToggle( value: _provaRequired, prostheticType: _selectedProstheticType, onChanged: (v) => setState(() => _provaRequired = v), ), const SizedBox(height: 16), // Teeth selection _SectionLabel( label: 'Dişler * (${_selectedTeeth.length} seçili)', ), const SizedBox(height: 6), // Bulk select row _TeethBulkBar( selectedTeeth: _selectedTeeth, onSelectAll: () => setState(() { _selectedTeeth.addAll([ for (int i = 11; i <= 18; i++) i, for (int i = 21; i <= 28; i++) i, for (int i = 31; i <= 38; i++) i, for (int i = 41; i <= 48; i++) i, ]); }), onSelectUpper: () => setState(() { final upper = {...[for (int i = 11; i <= 18; i++) i], ...[for (int i = 21; i <= 28; i++) i]}; if (upper.every(_selectedTeeth.contains)) { _selectedTeeth.removeAll(upper); } else { _selectedTeeth.addAll(upper); } }), onSelectLower: () => setState(() { final lower = {...[for (int i = 31; i <= 38; i++) i], ...[for (int i = 41; i <= 48; i++) i]}; if (lower.every(_selectedTeeth.contains)) { _selectedTeeth.removeAll(lower); } else { _selectedTeeth.addAll(lower); } }), onClear: () => setState(() => _selectedTeeth.clear()), ), const SizedBox(height: 8), _TeethGrid( selectedTeeth: _selectedTeeth, onToggle: (t) { setState(() { if (_selectedTeeth.contains(t)) { _selectedTeeth.remove(t); } else { _selectedTeeth.add(t); } }); }, ), const SizedBox(height: 16), // Color (optional) _SectionLabel(label: 'Renk (İsteğe Bağlı)'), TextFormField( controller: _colorController, decoration: const InputDecoration( hintText: 'Ör: A2, B3', ), ), const SizedBox(height: 16), // Description (optional) _SectionLabel(label: 'Açıklama (İsteğe Bağlı)'), TextFormField( controller: _descriptionController, decoration: const InputDecoration( hintText: 'Ek notlar...', ), minLines: 2, maxLines: 4, ), const SizedBox(height: 16), // Due date (optional) _SectionLabel(label: 'Son Tarih (İsteğe Bağlı)'), InkWell( onTap: _pickDueDate, child: InputDecorator( decoration: const InputDecoration( suffixIcon: Icon(Icons.calendar_today), ), child: Text( _dueDate != null ? '${_dueDate!.day.toString().padLeft(2, '0')}.${_dueDate!.month.toString().padLeft(2, '0')}.${_dueDate!.year} ${_dueDate!.hour.toString().padLeft(2, '0')}:${_dueDate!.minute.toString().padLeft(2, '0')}' : 'Tarih ve saat seçin', style: _dueDate != null ? null : TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ), ), const SizedBox(height: 16), // File attachments (optional) _SectionLabel(label: 'Dosya Ekle (İsteğe Bağlı)'), _FilePicker( files: _pendingFiles, onAdd: () async { final result = await FilePicker.platform.pickFiles( allowMultiple: true, withData: true, ); if (result != null) { setState(() => _pendingFiles.addAll(result.files)); } }, onRemove: (i) => setState(() => _pendingFiles.removeAt(i)), ), const SizedBox(height: 24), // Submit button if (_isSubmitting) const Center(child: CircularProgressIndicator()) else FilledButton.icon( onPressed: _submit, icon: const Icon(Icons.check), label: const Text('İş Oluştur'), style: FilledButton.styleFrom( minimumSize: const Size(double.infinity, 52), ), ), const SizedBox(height: 24), ], ), ), ); } } class _TeethBulkBar extends StatelessWidget { const _TeethBulkBar({ required this.selectedTeeth, required this.onSelectAll, required this.onSelectUpper, required this.onSelectLower, required this.onClear, }); final Set selectedTeeth; final VoidCallback onSelectAll; final VoidCallback onSelectUpper; final VoidCallback onSelectLower; final VoidCallback onClear; bool _allUpperSelected() { final upper = [for (int i = 11; i <= 18; i++) i, for (int i = 21; i <= 28; i++) i]; return upper.every(selectedTeeth.contains); } bool _allLowerSelected() { final lower = [for (int i = 31; i <= 38; i++) i, for (int i = 41; i <= 48; i++) i]; return lower.every(selectedTeeth.contains); } @override Widget build(BuildContext context) { final allSelected = _allUpperSelected() && _allLowerSelected(); return SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _BulkChip( label: 'Tüm Dişler', active: allSelected, onTap: allSelected ? onClear : onSelectAll, icon: Icons.select_all_rounded, ), const SizedBox(width: 6), _BulkChip( label: 'Üst Çene', active: _allUpperSelected(), onTap: onSelectUpper, icon: Icons.arrow_upward_rounded, ), const SizedBox(width: 6), _BulkChip( label: 'Alt Çene', active: _allLowerSelected(), onTap: onSelectLower, icon: Icons.arrow_downward_rounded, ), const SizedBox(width: 6), if (selectedTeeth.isNotEmpty) _BulkChip( label: 'Temizle', active: false, onTap: onClear, icon: Icons.clear_rounded, destructive: true, ), ], ), ); } } class _BulkChip extends StatelessWidget { const _BulkChip({ required this.label, required this.active, required this.onTap, required this.icon, this.destructive = false, }); final String label; final bool active; final VoidCallback onTap; final IconData icon; final bool destructive; @override Widget build(BuildContext context) { final color = destructive ? AppColors.cancelled : active ? AppColors.accent : AppColors.textSecondary; final bg = destructive ? AppColors.cancelledBg : active ? AppColors.inProgressBg : AppColors.surfaceVariant; return GestureDetector( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 120), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: bg, borderRadius: BorderRadius.circular(20), border: Border.all( color: active && !destructive ? AppColors.accent : AppColors.border, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 14, color: color), const SizedBox(width: 4), Text( label, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: color), ), ], ), ), ); } } class _TeethGrid extends StatelessWidget { const _TeethGrid({ required this.selectedTeeth, required this.onToggle, }); final Set selectedTeeth; final ValueChanged onToggle; @override Widget build(BuildContext context) { // Upper jaw: 18-11, 21-28 // Lower jaw: 48-41, 31-38 final upperRight = List.generate(8, (i) => 18 - i); // 18..11 final upperLeft = List.generate(8, (i) => 21 + i); // 21..28 final lowerRight = List.generate(8, (i) => 48 - i); // 48..41 final lowerLeft = List.generate(8, (i) => 31 + i); // 31..38 return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Upper jaw label Text('Üst Çene', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, )), const SizedBox(height: 4), Row( children: [ ...upperRight.map((t) => _ToothButton( tooth: t, selected: selectedTeeth.contains(t), onTap: () => onToggle(t), )), const VerticalDivider(width: 8), ...upperLeft.map((t) => _ToothButton( tooth: t, selected: selectedTeeth.contains(t), onTap: () => onToggle(t), )), ], ), const SizedBox(height: 8), // Lower jaw Text('Alt Çene', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, )), const SizedBox(height: 4), Row( children: [ ...lowerRight.map((t) => _ToothButton( tooth: t, selected: selectedTeeth.contains(t), onTap: () => onToggle(t), )), const VerticalDivider(width: 8), ...lowerLeft.map((t) => _ToothButton( tooth: t, selected: selectedTeeth.contains(t), onTap: () => onToggle(t), )), ], ), ], ); } } class _ToothButton extends StatelessWidget { const _ToothButton({ required this.tooth, required this.selected, required this.onTap, }); final int tooth; final bool selected; final VoidCallback onTap; @override Widget build(BuildContext context) { return Expanded( child: GestureDetector( onTap: onTap, child: Container( margin: const EdgeInsets.all(1.5), height: 36, decoration: BoxDecoration( color: selected ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(4), ), child: Center( child: Text( '$tooth', style: TextStyle( fontSize: 9, fontWeight: FontWeight.bold, color: selected ? Theme.of(context).colorScheme.onPrimary : Theme.of(context).colorScheme.onSurfaceVariant, ), ), ), ), ), ); } } class _FilePicker extends StatelessWidget { const _FilePicker({ required this.files, required this.onAdd, required this.onRemove, }); final List files; final VoidCallback onAdd; final void Function(int index) onRemove; @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (files.isNotEmpty) ...[ ...files.asMap().entries.map((e) { final i = e.key; final f = e.value; return Container( margin: const EdgeInsets.only(bottom: 6), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: AppColors.surfaceVariant, borderRadius: BorderRadius.circular(10), border: Border.all(color: AppColors.border), ), child: Row( children: [ const Icon(Icons.attach_file, size: 16, color: AppColors.textSecondary), const SizedBox(width: 8), Expanded( child: Text( f.name, style: const TextStyle( fontSize: 13, color: AppColors.textPrimary), overflow: TextOverflow.ellipsis, ), ), Text( _formatSize(f.size), style: const TextStyle( fontSize: 11, color: AppColors.textMuted), ), const SizedBox(width: 4), GestureDetector( onTap: () => onRemove(i), child: const Icon(Icons.close, size: 16, color: AppColors.textSecondary), ), ], ), ); }), const SizedBox(height: 4), ], OutlinedButton.icon( onPressed: onAdd, icon: const Icon(Icons.upload_file_outlined, size: 18), label: const Text('Dosya Seç'), style: OutlinedButton.styleFrom( foregroundColor: AppColors.accent, side: const BorderSide(color: AppColors.accent), ), ), ], ); } String _formatSize(int bytes) { if (bytes < 1024) return '$bytes B'; if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB'; return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB'; } } class _PricePreviewChip extends StatelessWidget { const _PricePreviewChip({required this.product, required this.effectivePrice}); final ProstheticProduct product; final double effectivePrice; @override Widget build(BuildContext context) { final currency = product.currency ?? 'TRY'; final unitPrice = product.unitPrice!; final hasDiscount = (effectivePrice - unitPrice).abs() > 0.01; return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: AppColors.successBg, borderRadius: BorderRadius.circular(10), border: Border.all(color: AppColors.success.withValues(alpha: 0.25)), ), child: Row( children: [ const Icon(Icons.sell_outlined, size: 16, color: AppColors.success), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${product.name} — ${effectivePrice.toStringAsFixed(2)} $currency', style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w700, color: AppColors.success), ), if (hasDiscount) Text( 'Liste: ${unitPrice.toStringAsFixed(2)} $currency · İndirim uygulandı', style: TextStyle(fontSize: 11, color: AppColors.success.withValues(alpha: 0.75)), ) else Text( 'Liste fiyatı · İndirim yok', style: TextStyle(fontSize: 11, color: AppColors.success.withValues(alpha: 0.75)), ), ], ), ), ], ), ); } } class _SectionLabel extends StatelessWidget { const _SectionLabel({required this.label}); final String label; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 6), child: Text( label, style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, ), ), ); } } class _ProvaToggle extends StatelessWidget { const _ProvaToggle({ required this.value, required this.onChanged, this.prostheticType, }); final bool value; final ValueChanged onChanged; final ProstheticType? prostheticType; @override Widget build(BuildContext context) { final steps = prostheticType != null ? jobStepTemplate(prostheticType!, value) : []; return Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), decoration: BoxDecoration( color: value ? AppColors.inProgressBg : AppColors.surfaceVariant, borderRadius: BorderRadius.circular(12), border: Border.all( color: value ? AppColors.inProgress.withValues(alpha: 0.3) : AppColors.border, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( value ? Icons.swap_horiz_rounded : Icons.straighten_rounded, size: 20, color: value ? AppColors.inProgress : AppColors.textSecondary, ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( value ? 'Provalı İş' : 'Provasız İş', style: TextStyle( fontWeight: FontWeight.w700, color: value ? AppColors.inProgress : AppColors.textPrimary, fontSize: 14, ), ), Text( value ? 'Lab her adımda klinik onayı bekler' : 'Lab doğrudan üretip teslime gönderir', style: const TextStyle( fontSize: 12, color: AppColors.textSecondary), ), ], ), ), Switch( value: value, onChanged: onChanged, activeColor: AppColors.inProgress, ), ], ), if (steps.isNotEmpty) ...[ const SizedBox(height: 8), Wrap( spacing: 6, children: steps.map((s) => Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(6), border: Border.all(color: AppColors.border), ), child: Text( s.label, style: const TextStyle( fontSize: 11, color: AppColors.textSecondary), ), )).toList(), ), ], ], ), ); } }