Apply clinic lab workflow feedback
This commit is contained in:
@@ -7,11 +7,9 @@ import '../../../models/prosthetic_product.dart';
|
||||
import 'lab_products_repository.dart';
|
||||
|
||||
const _prostheticTypes = [
|
||||
('metal_porselen', 'Metal Porselen'),
|
||||
('zirkonyum', 'Zirkonyum'),
|
||||
('implant_ustu_zirkonyum', 'İmplant Üstü Zirkonyum'),
|
||||
('sabit', 'Sabit'),
|
||||
('hareketli', 'Hareketli'),
|
||||
('gecici', 'Geçici'),
|
||||
('e_max', 'E-Max'),
|
||||
('diger', 'Diğer'),
|
||||
];
|
||||
|
||||
@@ -19,19 +17,42 @@ String _typeLabel(String value) {
|
||||
for (final t in _prostheticTypes) {
|
||||
if (t.$1 == value) return t.$2;
|
||||
}
|
||||
return value;
|
||||
return switch (value) {
|
||||
'metal_porselen' ||
|
||||
'zirkonyum' ||
|
||||
'implant_ustu_zirkonyum' ||
|
||||
'e_max' =>
|
||||
'Sabit',
|
||||
'tam_protez' || 'parsiyel' => 'Hareketli',
|
||||
_ => value,
|
||||
};
|
||||
}
|
||||
|
||||
String _typeValueForForm(String? value) {
|
||||
return switch (value) {
|
||||
'sabit' ||
|
||||
'metal_porselen' ||
|
||||
'zirkonyum' ||
|
||||
'implant_ustu_zirkonyum' ||
|
||||
'e_max' =>
|
||||
'sabit',
|
||||
'hareketli' || 'tam_protez' || 'parsiyel' => 'hareketli',
|
||||
'gecici' => 'gecici',
|
||||
'diger' => 'diger',
|
||||
_ => _prostheticTypes.first.$1,
|
||||
};
|
||||
}
|
||||
|
||||
// ── Adaptive sheet helper ────────────────────────────────────────────────────
|
||||
|
||||
void _showAdaptive(BuildContext context, Widget content) {
|
||||
final isDesktop = MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint;
|
||||
final isDesktop =
|
||||
MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint;
|
||||
if (isDesktop) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => Dialog(
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 560),
|
||||
child: content,
|
||||
@@ -98,8 +119,8 @@ class _LabProductsScreenState extends ConsumerState<LabProductsScreen> {
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('Ürünü Sil'),
|
||||
content: Text(
|
||||
'"${product.name}" ürününü silmek istediğinize emin misiniz?'),
|
||||
content:
|
||||
Text('"${product.name}" ürününü silmek istediğinize emin misiniz?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(false),
|
||||
@@ -107,8 +128,7 @@ class _LabProductsScreenState extends ConsumerState<LabProductsScreen> {
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(true),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: AppColors.cancelled),
|
||||
style: FilledButton.styleFrom(backgroundColor: AppColors.cancelled),
|
||||
child: const Text('Sil'),
|
||||
),
|
||||
],
|
||||
@@ -190,8 +210,7 @@ class _LabProductsScreenState extends ConsumerState<LabProductsScreen> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text('Hata: ${snap.error}',
|
||||
style: const TextStyle(
|
||||
color: AppColors.textSecondary)),
|
||||
style: const TextStyle(color: AppColors.textSecondary)),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton.icon(
|
||||
onPressed: _load,
|
||||
@@ -206,9 +225,11 @@ class _LabProductsScreenState extends ConsumerState<LabProductsScreen> {
|
||||
final q = _searchQuery.toLowerCase().trim();
|
||||
final products = q.isEmpty
|
||||
? allProducts
|
||||
: allProducts.where((p) =>
|
||||
p.name.toLowerCase().contains(q) ||
|
||||
_typeLabel(p.prostheticType).toLowerCase().contains(q)).toList();
|
||||
: allProducts
|
||||
.where((p) =>
|
||||
p.name.toLowerCase().contains(q) ||
|
||||
_typeLabel(p.prostheticType).toLowerCase().contains(q))
|
||||
.toList();
|
||||
|
||||
if (allProducts.isEmpty) {
|
||||
return Center(
|
||||
@@ -226,18 +247,21 @@ class _LabProductsScreenState extends ConsumerState<LabProductsScreen> {
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
q.isNotEmpty ? 'Sonuç bulunamadı' : 'Henüz ürün eklenmedi',
|
||||
q.isNotEmpty
|
||||
? 'Sonuç bulunamadı'
|
||||
: 'Henüz ürün eklenmedi',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (q.isEmpty) FilledButton.icon(
|
||||
onPressed: () => _showProductSheet(),
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('İlk Ürünü Ekle'),
|
||||
),
|
||||
if (q.isEmpty)
|
||||
FilledButton.icon(
|
||||
onPressed: () => _showProductSheet(),
|
||||
icon: const Icon(Icons.add),
|
||||
label: const Text('İlk Ürünü Ekle'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -260,7 +284,10 @@ class _LabProductsScreenState extends ConsumerState<LabProductsScreen> {
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'Sonuç bulunamadı',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary),
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -272,10 +299,12 @@ class _LabProductsScreenState extends ConsumerState<LabProductsScreen> {
|
||||
itemCount: products.length,
|
||||
itemBuilder: (ctx, i) {
|
||||
final product = products[i];
|
||||
final statusColor =
|
||||
product.isActive ? AppColors.inProgress : AppColors.textMuted;
|
||||
final statusBg =
|
||||
product.isActive ? AppColors.inProgressBg : AppColors.surfaceVariant;
|
||||
final statusColor = product.isActive
|
||||
? AppColors.inProgress
|
||||
: AppColors.textMuted;
|
||||
final statusBg = product.isActive
|
||||
? AppColors.inProgressBg
|
||||
: AppColors.surfaceVariant;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: GestureDetector(
|
||||
@@ -293,8 +322,7 @@ class _LabProductsScreenState extends ConsumerState<LabProductsScreen> {
|
||||
border: Border.all(color: AppColors.border),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color:
|
||||
Colors.black.withValues(alpha: 0.04),
|
||||
color: Colors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2))
|
||||
]),
|
||||
@@ -411,7 +439,7 @@ class _ProductFormState extends State<_ProductForm> {
|
||||
_priceCtrl = TextEditingController(
|
||||
text: p?.unitPrice != null ? p!.unitPrice!.toString() : '');
|
||||
_descCtrl = TextEditingController(text: p?.description ?? '');
|
||||
_selectedType = p?.prostheticType ?? _prostheticTypes.first.$1;
|
||||
_selectedType = _typeValueForForm(p?.prostheticType);
|
||||
_currency = p?.currency ?? 'TRY';
|
||||
_isActive = p?.isActive ?? true;
|
||||
}
|
||||
@@ -437,8 +465,7 @@ class _ProductFormState extends State<_ProductForm> {
|
||||
unitPrice: price,
|
||||
currency: _currency,
|
||||
isActive: _isActive,
|
||||
description:
|
||||
_descCtrl.text.trim().isEmpty ? null : _descCtrl.text.trim(),
|
||||
description: _descCtrl.text.trim().isEmpty ? null : _descCtrl.text.trim(),
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -466,7 +493,8 @@ class _ProductFormState extends State<_ProductForm> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDesktop = MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint;
|
||||
final isDesktop =
|
||||
MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint;
|
||||
final isEdit = widget.existing != null;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
@@ -479,9 +507,7 @@ class _ProductFormState extends State<_ProductForm> {
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 24,
|
||||
bottom: isDesktop
|
||||
? 24
|
||||
: MediaQuery.of(context).viewInsets.bottom + 24,
|
||||
bottom: isDesktop ? 24 : MediaQuery.of(context).viewInsets.bottom + 24,
|
||||
),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
@@ -495,17 +521,14 @@ class _ProductFormState extends State<_ProductForm> {
|
||||
Expanded(
|
||||
child: Text(
|
||||
isEdit ? 'Ürünü Düzenle' : 'Yeni Ürün',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.textPrimary),
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.textPrimary),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close,
|
||||
color: AppColors.textSecondary),
|
||||
icon:
|
||||
const Icon(Icons.close, color: AppColors.textSecondary),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
@@ -515,8 +538,7 @@ class _ProductFormState extends State<_ProductForm> {
|
||||
// Name
|
||||
TextFormField(
|
||||
controller: _nameCtrl,
|
||||
decoration:
|
||||
const InputDecoration(labelText: 'Ürün Adı *'),
|
||||
decoration: const InputDecoration(labelText: 'Ürün Adı *'),
|
||||
validator: (v) =>
|
||||
v == null || v.trim().isEmpty ? 'Ürün adı gerekli' : null,
|
||||
),
|
||||
@@ -525,8 +547,7 @@ class _ProductFormState extends State<_ProductForm> {
|
||||
// Prosthetic type dropdown
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: _selectedType,
|
||||
decoration:
|
||||
const InputDecoration(labelText: 'Protez Tipi *'),
|
||||
decoration: const InputDecoration(labelText: 'Protez Tipi *'),
|
||||
items: _prostheticTypes
|
||||
.map((t) => DropdownMenuItem(
|
||||
value: t.$1,
|
||||
@@ -546,8 +567,8 @@ class _ProductFormState extends State<_ProductForm> {
|
||||
controller: _priceCtrl,
|
||||
decoration:
|
||||
const InputDecoration(labelText: 'Birim Fiyat'),
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true),
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(decimal: true),
|
||||
validator: (v) {
|
||||
if (v != null && v.isNotEmpty) {
|
||||
if (double.tryParse(v) == null) {
|
||||
@@ -581,8 +602,8 @@ class _ProductFormState extends State<_ProductForm> {
|
||||
// Description
|
||||
TextFormField(
|
||||
controller: _descCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Açıklama (isteğe bağlı)'),
|
||||
decoration:
|
||||
const InputDecoration(labelText: 'Açıklama (isteğe bağlı)'),
|
||||
maxLines: 2,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
Reference in New Issue
Block a user