Add pricing entry flow and platform admin foundations
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
@@ -9,6 +10,7 @@ import '../../../core/router/app_router.dart';
|
||||
import '../../../core/services/realtime_service.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../../core/widgets/tooth_logo.dart';
|
||||
import '../../shared/location_completion_banner.dart';
|
||||
import '../../../models/job.dart';
|
||||
import '../jobs/clinic_jobs_repository.dart';
|
||||
import '../patients/clinic_patients_repository.dart';
|
||||
@@ -23,28 +25,48 @@ class ClinicDashboardScreen extends ConsumerStatefulWidget {
|
||||
|
||||
class _ClinicDashboardScreenState extends ConsumerState<ClinicDashboardScreen> {
|
||||
late Future<_DashboardData> _future;
|
||||
late UnsubFn _unsub;
|
||||
UnsubFn? _unsub;
|
||||
final Map<String, bool> _actingJobs = {};
|
||||
Timer? _reloadDebounce;
|
||||
String? _subscribedTenantId;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_load();
|
||||
final tenantId = ref.read(authProvider).activeTenant!.tenant.id;
|
||||
_unsub = RealtimeService.instance.watch(
|
||||
'jobs',
|
||||
filter: "clinic_tenant_id='$tenantId'",
|
||||
onEvent: (_) { if (mounted) _load(); },
|
||||
);
|
||||
_ensureRealtimeSubscription();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_unsub();
|
||||
_reloadDebounce?.cancel();
|
||||
_unsub?.call();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _ensureRealtimeSubscription() {
|
||||
final tenantId = ref.read(authProvider).activeTenant?.tenant.id;
|
||||
if (tenantId == null || tenantId == _subscribedTenantId) return;
|
||||
_unsub?.call();
|
||||
_subscribedTenantId = tenantId;
|
||||
_unsub = RealtimeService.instance.watch(
|
||||
'jobs',
|
||||
filter: "clinic_tenant_id='$tenantId'",
|
||||
onEvent: (_) {
|
||||
_scheduleReload();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _scheduleReload() {
|
||||
_reloadDebounce?.cancel();
|
||||
_reloadDebounce = Timer(const Duration(milliseconds: 250), () {
|
||||
if (mounted) _load();
|
||||
});
|
||||
}
|
||||
|
||||
void _load() {
|
||||
_ensureRealtimeSubscription();
|
||||
final tenantId = ref.read(authProvider).activeTenant!.tenant.id;
|
||||
setState(() {
|
||||
_future = _loadAll(tenantId);
|
||||
@@ -58,7 +80,9 @@ class _ClinicDashboardScreenState extends ConsumerState<ClinicDashboardScreen> {
|
||||
title: Text(job.patientCode),
|
||||
content: Text('${job.prostheticType.label} işini onaylıyor musunuz?'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('İptal')),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, false),
|
||||
child: const Text('İptal')),
|
||||
FilledButton(
|
||||
style: FilledButton.styleFrom(backgroundColor: AppColors.success),
|
||||
onPressed: () => Navigator.pop(ctx, true),
|
||||
@@ -73,7 +97,10 @@ class _ClinicDashboardScreenState extends ConsumerState<ClinicDashboardScreen> {
|
||||
await ClinicJobsRepository.instance.approveAtClinic(job.id, job);
|
||||
_load();
|
||||
} catch (e) {
|
||||
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Hata: $e')));
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(SnackBar(content: Text('Hata: $e')));
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _actingJobs.remove(job.id));
|
||||
}
|
||||
@@ -84,9 +111,12 @@ class _ClinicDashboardScreenState extends ConsumerState<ClinicDashboardScreen> {
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: Text(job.patientCode),
|
||||
content: Text('${job.prostheticType.label} işi teslim alındı olarak işaretlensin mi?'),
|
||||
content: Text(
|
||||
'${job.prostheticType.label} işi teslim alındı olarak işaretlensin mi?'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('İptal')),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, false),
|
||||
child: const Text('İptal')),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(ctx, true),
|
||||
child: const Text('Teslim Aldım'),
|
||||
@@ -100,7 +130,10 @@ class _ClinicDashboardScreenState extends ConsumerState<ClinicDashboardScreen> {
|
||||
await ClinicJobsRepository.instance.markDelivered(job.id, job);
|
||||
_load();
|
||||
} catch (e) {
|
||||
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Hata: $e')));
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(SnackBar(content: Text('Hata: $e')));
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _actingJobs.remove(job.id));
|
||||
}
|
||||
@@ -112,18 +145,25 @@ class _ClinicDashboardScreenState extends ConsumerState<ClinicDashboardScreen> {
|
||||
final lastMonthStart = DateTime(now.year, now.month - 1, 1);
|
||||
|
||||
final results = await Future.wait([
|
||||
ClinicJobsRepository.instance.listOutbound(tenantId, statuses: ['pending'], limit: 200),
|
||||
ClinicJobsRepository.instance.listOutbound(tenantId, statuses: ['in_progress'], limit: 200),
|
||||
ClinicJobsRepository.instance.listOutbound(tenantId, statuses: ['sent'], limit: 200),
|
||||
ClinicJobsRepository.instance
|
||||
.listOutbound(tenantId, statuses: ['pending'], limit: 200),
|
||||
ClinicJobsRepository.instance
|
||||
.listOutbound(tenantId, statuses: ['in_progress'], limit: 200),
|
||||
ClinicJobsRepository.instance
|
||||
.listOutbound(tenantId, statuses: ['sent'], limit: 200),
|
||||
ClinicJobsRepository.instance.listOutbound(tenantId, limit: 5),
|
||||
ClinicPatientsRepository.instance.listPatients(tenantId, limit: 200),
|
||||
]);
|
||||
final thisMonth = await ClinicJobsRepository.instance.countDelivered(tenantId, from: thisMonthStart);
|
||||
final lastMonth = await ClinicJobsRepository.instance.countDelivered(tenantId, from: lastMonthStart, to: thisMonthStart);
|
||||
final thisMonth = await ClinicJobsRepository.instance
|
||||
.countDelivered(tenantId, from: thisMonthStart);
|
||||
final lastMonth = await ClinicJobsRepository.instance
|
||||
.countDelivered(tenantId, from: lastMonthStart, to: thisMonthStart);
|
||||
|
||||
final inProgressJobs = results[1] as List<Job>;
|
||||
final sentJobs = results[2] as List<Job>;
|
||||
final provaAtClinic = inProgressJobs.where((j) => j.location == JobLocation.atClinic).toList();
|
||||
final provaAtClinic = inProgressJobs
|
||||
.where((j) => j.location == JobLocation.atClinic)
|
||||
.toList();
|
||||
final actionJobs = [...provaAtClinic, ...sentJobs];
|
||||
|
||||
return _DashboardData(
|
||||
@@ -140,8 +180,10 @@ class _ClinicDashboardScreenState extends ConsumerState<ClinicDashboardScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final companyName =
|
||||
ref.watch(authProvider).activeTenant?.tenant.companyName ?? '';
|
||||
_ensureRealtimeSubscription();
|
||||
final activeTenant = ref.watch(authProvider).activeTenant?.tenant;
|
||||
final companyName = activeTenant?.companyName ?? '';
|
||||
final showLocationWarning = activeTenant?.hasLocation != true;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.background,
|
||||
@@ -159,16 +201,31 @@ class _ClinicDashboardScreenState extends ConsumerState<ClinicDashboardScreen> {
|
||||
future: _future,
|
||||
builder: (ctx, snap) {
|
||||
if (snap.connectionState == ConnectionState.waiting) {
|
||||
return _DashboardSkeleton(companyName: companyName, hPad: hPad);
|
||||
return _DashboardSkeleton(
|
||||
companyName: companyName, hPad: hPad);
|
||||
}
|
||||
if (snap.hasError) {
|
||||
return _ErrorBody(onRetry: _load);
|
||||
}
|
||||
final data = snap.data!;
|
||||
final isDesktop = MediaQuery.sizeOf(ctx).width > AppLayout.sidebarBreakpoint;
|
||||
final isDesktop =
|
||||
MediaQuery.sizeOf(ctx).width > AppLayout.sidebarBreakpoint;
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
_DashboardHeader(companyName: companyName),
|
||||
if (showLocationWarning)
|
||||
SliverPadding(
|
||||
padding: EdgeInsets.fromLTRB(hPad, 16, hPad, 0),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: LocationCompletionBanner(
|
||||
title: 'Konum kaydı eksik',
|
||||
description:
|
||||
'Haritada görünmek ve yakın laboratuvar sıralamasında doğru yer almak için işletme konumunu tamamlayın.',
|
||||
buttonLabel: 'Konumu Tamamla',
|
||||
onTap: () => context.go(routeClinicSettings),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isDesktop)
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||
@@ -186,14 +243,18 @@ class _ClinicDashboardScreenState extends ConsumerState<ClinicDashboardScreen> {
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: _MonthlyReportSection(data: data)
|
||||
.animate().fadeIn(duration: 300.ms).slideY(begin: 0.08, end: 0),
|
||||
.animate()
|
||||
.fadeIn(duration: 300.ms)
|
||||
.slideY(begin: 0.08, end: 0),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 0),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: _GamificationRow(data: data)
|
||||
.animate().fadeIn(duration: 300.ms, delay: 60.ms).slideY(begin: 0.08, end: 0),
|
||||
.animate()
|
||||
.fadeIn(duration: 300.ms, delay: 60.ms)
|
||||
.slideY(begin: 0.08, end: 0),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -208,7 +269,10 @@ class _ClinicDashboardScreenState extends ConsumerState<ClinicDashboardScreen> {
|
||||
minimumSize: const Size(double.infinity, 52),
|
||||
backgroundColor: AppColors.accent,
|
||||
),
|
||||
).animate().fadeIn(duration: 300.ms).slideY(begin: 0.1, end: 0),
|
||||
)
|
||||
.animate()
|
||||
.fadeIn(duration: 300.ms)
|
||||
.slideY(begin: 0.1, end: 0),
|
||||
),
|
||||
),
|
||||
if (data.actionJobs.isNotEmpty)
|
||||
@@ -220,7 +284,10 @@ class _ClinicDashboardScreenState extends ConsumerState<ClinicDashboardScreen> {
|
||||
actingJobs: _actingJobs,
|
||||
onApprove: _approveAtClinic,
|
||||
onDeliver: _markDelivered,
|
||||
).animate().fadeIn(duration: 300.ms).slideY(begin: 0.06, end: 0),
|
||||
)
|
||||
.animate()
|
||||
.fadeIn(duration: 300.ms)
|
||||
.slideY(begin: 0.06, end: 0),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
@@ -235,7 +302,8 @@ class _ClinicDashboardScreenState extends ConsumerState<ClinicDashboardScreen> {
|
||||
onPressed: () => context.go(routeClinicJobs),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: AppColors.accent,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8),
|
||||
),
|
||||
child: const Text('Tümünü Gör'),
|
||||
),
|
||||
@@ -251,7 +319,8 @@ class _ClinicDashboardScreenState extends ConsumerState<ClinicDashboardScreen> {
|
||||
padding: EdgeInsets.fromLTRB(hPad, 0, hPad, 24),
|
||||
sliver: SliverList.separated(
|
||||
itemCount: data.recentJobs.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 10),
|
||||
separatorBuilder: (_, __) =>
|
||||
const SizedBox(height: 10),
|
||||
itemBuilder: (ctx, i) =>
|
||||
_JobCard(job: data.recentJobs[i])
|
||||
.animate(delay: (i * 60).ms)
|
||||
@@ -319,30 +388,47 @@ class _ActionSection extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 26, height: 26,
|
||||
decoration: BoxDecoration(color: AppColors.pending, borderRadius: BorderRadius.circular(7)),
|
||||
child: const Icon(Icons.priority_high_rounded, size: 15, color: Colors.white),
|
||||
width: 26,
|
||||
height: 26,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.pending,
|
||||
borderRadius: BorderRadius.circular(7)),
|
||||
child: const Icon(Icons.priority_high_rounded,
|
||||
size: 15, color: Colors.white),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text('Yapılacaklar', style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700)),
|
||||
Text('Yapılacaklar',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.w700)),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
|
||||
decoration: BoxDecoration(color: AppColors.pending, borderRadius: BorderRadius.circular(10)),
|
||||
child: Text('${jobs.length}', style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w800, color: Colors.white)),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.pending,
|
||||
borderRadius: BorderRadius.circular(10)),
|
||||
child: Text('${jobs.length}',
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Colors.white)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...jobs.asMap().entries.map((entry) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: _ActionJobCard(
|
||||
job: entry.value,
|
||||
acting: actingJobs[entry.value.id] == true,
|
||||
onApprove: () => onApprove(entry.value),
|
||||
onDeliver: () => onDeliver(entry.value),
|
||||
).animate(delay: (entry.key * 50).ms).fadeIn(duration: 250.ms).slideY(begin: 0.08, end: 0),
|
||||
)),
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: _ActionJobCard(
|
||||
job: entry.value,
|
||||
acting: actingJobs[entry.value.id] == true,
|
||||
onApprove: () => onApprove(entry.value),
|
||||
onDeliver: () => onDeliver(entry.value),
|
||||
)
|
||||
.animate(delay: (entry.key * 50).ms)
|
||||
.fadeIn(duration: 250.ms)
|
||||
.slideY(begin: 0.08, end: 0),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -361,15 +447,18 @@ class _ActionJobCard extends StatelessWidget {
|
||||
final VoidCallback onApprove;
|
||||
final VoidCallback onDeliver;
|
||||
|
||||
bool get _isProva => job.status == JobStatus.inProgress && job.location == JobLocation.atClinic;
|
||||
bool get _isProva =>
|
||||
job.status == JobStatus.inProgress &&
|
||||
job.location == JobLocation.atClinic;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isProva = _isProva;
|
||||
final borderColor = isProva ? AppColors.pending : AppColors.accent;
|
||||
final bgColor = isProva ? AppColors.pendingBg : AppColors.inProgressBg;
|
||||
final iconColor = isProva ? AppColors.pending : AppColors.accent;
|
||||
final icon = isProva ? Icons.rate_review_outlined : Icons.inventory_2_outlined;
|
||||
final bgColor = isProva ? AppColors.pendingBg : AppColors.inProgressBg;
|
||||
final iconColor = isProva ? AppColors.pending : AppColors.accent;
|
||||
final icon =
|
||||
isProva ? Icons.rate_review_outlined : Icons.inventory_2_outlined;
|
||||
final statusLabel = isProva ? 'Onay Bekliyor' : 'Teslimat Bekliyor';
|
||||
|
||||
return Semantics(
|
||||
@@ -385,8 +474,14 @@ class _ActionJobCard extends StatelessWidget {
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: borderColor.withValues(alpha: 0.45), width: 1.5),
|
||||
boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 3))],
|
||||
border: Border.all(
|
||||
color: borderColor.withValues(alpha: 0.45), width: 1.5),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 3))
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -396,8 +491,11 @@ class _ActionJobCard extends StatelessWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40, height: 40,
|
||||
decoration: BoxDecoration(color: bgColor, borderRadius: BorderRadius.circular(11)),
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(11)),
|
||||
child: Icon(icon, color: iconColor, size: 19),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
@@ -405,21 +503,34 @@ class _ActionJobCard extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(job.patientCode, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.textPrimary)),
|
||||
Text(job.patientCode,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary)),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'${job.prostheticType.label} · ${job.labName ?? 'Lab'}',
|
||||
style: const TextStyle(fontSize: 12, color: AppColors.textSecondary),
|
||||
maxLines: 1, overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 12, color: AppColors.textSecondary),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(color: bgColor, borderRadius: BorderRadius.circular(8)),
|
||||
child: Text(statusLabel, style: TextStyle(fontSize: 10, fontWeight: FontWeight.w700, color: iconColor)),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(8)),
|
||||
child: Text(statusLabel,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: iconColor)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -427,14 +538,20 @@ class _ActionJobCard extends StatelessWidget {
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor.withValues(alpha: 0.45),
|
||||
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(13), bottomRight: Radius.circular(13)),
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(13),
|
||||
bottomRight: Radius.circular(13)),
|
||||
),
|
||||
padding: const EdgeInsets.fromLTRB(12, 8, 12, 10),
|
||||
child: acting
|
||||
? const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4),
|
||||
child: SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2.5, color: AppColors.accent)),
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.5, color: AppColors.accent)),
|
||||
),
|
||||
)
|
||||
: isProva
|
||||
@@ -442,27 +559,38 @@ class _ActionJobCard extends StatelessWidget {
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: onApprove,
|
||||
icon: const Icon(Icons.check_circle_outline, size: 15),
|
||||
label: const Text('Onayla', style: TextStyle(fontSize: 13)),
|
||||
icon: const Icon(Icons.check_circle_outline,
|
||||
size: 15),
|
||||
label: const Text('Onayla',
|
||||
style: TextStyle(fontSize: 13)),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: AppColors.success,
|
||||
minimumSize: const Size(0, 36),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
tapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => context.push('/clinic/jobs/${job.id}'),
|
||||
icon: const Icon(Icons.open_in_new_rounded, size: 14),
|
||||
label: const Text('Detay', style: TextStyle(fontSize: 13)),
|
||||
onPressed: () =>
|
||||
context.push('/clinic/jobs/${job.id}'),
|
||||
icon: const Icon(Icons.open_in_new_rounded,
|
||||
size: 14),
|
||||
label: const Text('Detay',
|
||||
style: TextStyle(fontSize: 13)),
|
||||
style: OutlinedButton.styleFrom(
|
||||
minimumSize: const Size(0, 36),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
tapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12),
|
||||
foregroundColor: AppColors.pending,
|
||||
side: BorderSide(color: AppColors.pending.withValues(alpha: 0.6)),
|
||||
side: BorderSide(
|
||||
color: AppColors.pending
|
||||
.withValues(alpha: 0.6)),
|
||||
),
|
||||
),
|
||||
])
|
||||
@@ -470,26 +598,37 @@ class _ActionJobCard extends StatelessWidget {
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
onPressed: onDeliver,
|
||||
icon: const Icon(Icons.inventory_2_outlined, size: 15),
|
||||
label: const Text('Teslim Aldım', style: TextStyle(fontSize: 13)),
|
||||
icon: const Icon(Icons.inventory_2_outlined,
|
||||
size: 15),
|
||||
label: const Text('Teslim Aldım',
|
||||
style: TextStyle(fontSize: 13)),
|
||||
style: FilledButton.styleFrom(
|
||||
minimumSize: const Size(0, 36),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
tapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => context.push('/clinic/jobs/${job.id}'),
|
||||
icon: const Icon(Icons.open_in_new_rounded, size: 14),
|
||||
label: const Text('Detay', style: TextStyle(fontSize: 13)),
|
||||
onPressed: () =>
|
||||
context.push('/clinic/jobs/${job.id}'),
|
||||
icon: const Icon(Icons.open_in_new_rounded,
|
||||
size: 14),
|
||||
label: const Text('Detay',
|
||||
style: TextStyle(fontSize: 13)),
|
||||
style: OutlinedButton.styleFrom(
|
||||
minimumSize: const Size(0, 36),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
tapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12),
|
||||
foregroundColor: AppColors.accent,
|
||||
side: BorderSide(color: AppColors.accent.withValues(alpha: 0.6)),
|
||||
side: BorderSide(
|
||||
color: AppColors.accent
|
||||
.withValues(alpha: 0.6)),
|
||||
),
|
||||
),
|
||||
]),
|
||||
@@ -526,20 +665,34 @@ class _MonthlyReportSection extends StatelessWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.bar_chart_rounded, size: 18, color: AppColors.accent),
|
||||
const Icon(Icons.bar_chart_rounded,
|
||||
size: 18, color: AppColors.accent),
|
||||
const SizedBox(width: 6),
|
||||
Text('Aylık Rapor', style: Theme.of(context).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600)),
|
||||
Text('Aylık Rapor',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleSmall
|
||||
?.copyWith(fontWeight: FontWeight.w600)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: _MonthStat(label: 'Bu Ay', value: data.thisMonthDelivered, highlighted: true)),
|
||||
Expanded(
|
||||
child: _MonthStat(
|
||||
label: 'Bu Ay',
|
||||
value: data.thisMonthDelivered,
|
||||
highlighted: true)),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: _MonthStat(label: 'Geçen Ay', value: data.lastMonthDelivered, highlighted: false)),
|
||||
Expanded(
|
||||
child: _MonthStat(
|
||||
label: 'Geçen Ay',
|
||||
value: data.lastMonthDelivered,
|
||||
highlighted: false)),
|
||||
const SizedBox(width: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: isUp ? AppColors.successBg : AppColors.cancelledBg,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
@@ -548,7 +701,9 @@ class _MonthlyReportSection extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
isUp ? Icons.trending_up_rounded : Icons.trending_down_rounded,
|
||||
isUp
|
||||
? Icons.trending_up_rounded
|
||||
: Icons.trending_down_rounded,
|
||||
size: 16,
|
||||
color: isUp ? AppColors.success : AppColors.cancelled,
|
||||
),
|
||||
@@ -573,7 +728,8 @@ class _MonthlyReportSection extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _MonthStat extends StatelessWidget {
|
||||
const _MonthStat({required this.label, required this.value, required this.highlighted});
|
||||
const _MonthStat(
|
||||
{required this.label, required this.value, required this.highlighted});
|
||||
final String label;
|
||||
final int value;
|
||||
final bool highlighted;
|
||||
@@ -583,14 +739,22 @@ class _MonthStat extends StatelessWidget {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: highlighted ? AppColors.accent.withValues(alpha: 0.06) : AppColors.background,
|
||||
color: highlighted
|
||||
? AppColors.accent.withValues(alpha: 0.06)
|
||||
: AppColors.background,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: highlighted ? Border.all(color: AppColors.accent.withValues(alpha: 0.2)) : null,
|
||||
border: highlighted
|
||||
? Border.all(color: AppColors.accent.withValues(alpha: 0.2))
|
||||
: null,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label, style: TextStyle(fontSize: 11, color: AppColors.textSecondary, fontWeight: FontWeight.w500)),
|
||||
Text(label,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
color: AppColors.textSecondary,
|
||||
fontWeight: FontWeight.w500)),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'$value iş',
|
||||
@@ -617,7 +781,8 @@ class _GamificationRow extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final progress = (data.thisMonthDelivered / _monthlyGoal).clamp(0.0, 1.0);
|
||||
final remaining = (_monthlyGoal - data.thisMonthDelivered).clamp(0, _monthlyGoal);
|
||||
final remaining =
|
||||
(_monthlyGoal - data.thisMonthDelivered).clamp(0, _monthlyGoal);
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
@@ -632,7 +797,11 @@ class _GamificationRow extends StatelessWidget {
|
||||
children: [
|
||||
const Text('🏆', style: TextStyle(fontSize: 16)),
|
||||
const SizedBox(width: 6),
|
||||
Text('Aylık Hedef', style: Theme.of(context).textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600)),
|
||||
Text('Aylık Hedef',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleSmall
|
||||
?.copyWith(fontWeight: FontWeight.w600)),
|
||||
const Spacer(),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
@@ -642,7 +811,10 @@ class _GamificationRow extends StatelessWidget {
|
||||
),
|
||||
child: Text(
|
||||
'${data.points} puan',
|
||||
style: const TextStyle(fontSize: 11, fontWeight: FontWeight.w700, color: AppColors.primary),
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primary),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -665,14 +837,17 @@ class _GamificationRow extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
'${data.thisMonthDelivered} / $_monthlyGoal iş teslim edildi',
|
||||
style: TextStyle(fontSize: 12, color: AppColors.textSecondary),
|
||||
style: const TextStyle(
|
||||
fontSize: 12, color: AppColors.textSecondary),
|
||||
),
|
||||
Text(
|
||||
progress >= 1.0 ? 'Hedef tamamlandı!' : '$remaining iş kaldı',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: progress >= 1.0 ? AppColors.success : AppColors.textSecondary,
|
||||
color: progress >= 1.0
|
||||
? AppColors.success
|
||||
: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -693,7 +868,8 @@ class _DashboardHeader extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDesktop = MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint;
|
||||
final isDesktop =
|
||||
MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint;
|
||||
|
||||
if (isDesktop) {
|
||||
return SliverAppBar(
|
||||
@@ -712,15 +888,24 @@ class _DashboardHeader extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('Genel Bakış', style: TextStyle(fontSize: 11, color: AppColors.textSecondary.withValues(alpha: 0.8), letterSpacing: 0.3)),
|
||||
const Text('Bugünkü Durum', style: TextStyle(fontSize: 17, fontWeight: FontWeight.w700, color: AppColors.textPrimary)),
|
||||
Text('Genel Bakış',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: AppColors.textSecondary.withValues(alpha: 0.8),
|
||||
letterSpacing: 0.3)),
|
||||
const Text('Bugünkü Durum',
|
||||
style: TextStyle(
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary)),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => context.go(routeClinicSettings),
|
||||
icon: const Icon(Icons.settings_outlined, color: AppColors.textSecondary, size: 22),
|
||||
icon: const Icon(Icons.settings_outlined,
|
||||
color: AppColors.textSecondary, size: 22),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
@@ -751,14 +936,26 @@ class _DashboardHeader extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text('DLS', style: TextStyle(color: Colors.white.withValues(alpha: 0.65), fontSize: 11, fontWeight: FontWeight.w600, letterSpacing: 1.5)),
|
||||
Text(companyName, style: const TextStyle(color: Colors.white, fontSize: 15, fontWeight: FontWeight.w700), maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
Text('DLS',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.65),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 1.5)),
|
||||
Text(companyName,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w700),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => context.go(routeClinicSettings),
|
||||
icon: const Icon(Icons.settings_outlined, color: Colors.white, size: 22),
|
||||
icon: const Icon(Icons.settings_outlined,
|
||||
color: Colors.white, size: 22),
|
||||
),
|
||||
],
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
@@ -777,9 +974,19 @@ class _DashboardHeader extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text('Genel Bakış', style: TextStyle(color: Colors.white.withValues(alpha: 0.65), fontSize: 12, fontWeight: FontWeight.w500, letterSpacing: 0.5)),
|
||||
Text('Genel Bakış',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.65),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.5)),
|
||||
const SizedBox(height: 4),
|
||||
const Text('Bugünkü Durum', style: TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.w800, letterSpacing: -0.5)),
|
||||
const Text('Bugünkü Durum',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: -0.5)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -805,19 +1012,48 @@ class _StatsRow extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isWideDesktop = MediaQuery.sizeOf(context).width >= AppLayout.wideBreakpoint;
|
||||
final isWideDesktop =
|
||||
MediaQuery.sizeOf(context).width >= AppLayout.wideBreakpoint;
|
||||
|
||||
final c1 = _StatCard(label: 'Bekleyen', value: '$pending', icon: Icons.hourglass_top_rounded, color: AppColors.pending, bgColor: AppColors.pendingBg)
|
||||
.animate().fadeIn(duration: 350.ms).slideY(begin: 0.2, end: 0);
|
||||
final c2 = _StatCard(label: 'Devam Eden', value: '$inProgress', icon: Icons.autorenew_rounded, color: AppColors.inProgress, bgColor: AppColors.inProgressBg)
|
||||
.animate(delay: 80.ms).fadeIn(duration: 350.ms).slideY(begin: 0.2, end: 0);
|
||||
final c3 = _StatCard(label: 'Toplam Hasta', value: '$patients', icon: Icons.people_outline_rounded, color: AppColors.success, bgColor: AppColors.successBg)
|
||||
.animate(delay: 160.ms).fadeIn(duration: 350.ms).slideY(begin: 0.2, end: 0);
|
||||
final c1 = _StatCard(
|
||||
label: 'Bekleyen',
|
||||
value: '$pending',
|
||||
icon: Icons.hourglass_top_rounded,
|
||||
color: AppColors.pending,
|
||||
bgColor: AppColors.pendingBg)
|
||||
.animate()
|
||||
.fadeIn(duration: 350.ms)
|
||||
.slideY(begin: 0.2, end: 0);
|
||||
final c2 = _StatCard(
|
||||
label: 'Devam Eden',
|
||||
value: '$inProgress',
|
||||
icon: Icons.autorenew_rounded,
|
||||
color: AppColors.inProgress,
|
||||
bgColor: AppColors.inProgressBg)
|
||||
.animate(delay: 80.ms)
|
||||
.fadeIn(duration: 350.ms)
|
||||
.slideY(begin: 0.2, end: 0);
|
||||
final c3 = _StatCard(
|
||||
label: 'Toplam Hasta',
|
||||
value: '$patients',
|
||||
icon: Icons.people_outline_rounded,
|
||||
color: AppColors.success,
|
||||
bgColor: AppColors.successBg)
|
||||
.animate(delay: 160.ms)
|
||||
.fadeIn(duration: 350.ms)
|
||||
.slideY(begin: 0.2, end: 0);
|
||||
|
||||
// Wide desktop (≥ 1100px): 4 cards side by side — full lifecycle view.
|
||||
if (isWideDesktop) {
|
||||
final c4 = _StatCard(label: 'Klinik\'te', value: '$sent', icon: Icons.local_hospital_outlined, color: AppColors.accent, bgColor: AppColors.inProgressBg)
|
||||
.animate(delay: 120.ms).fadeIn(duration: 350.ms).slideY(begin: 0.2, end: 0);
|
||||
final c4 = _StatCard(
|
||||
label: 'Klinik\'te',
|
||||
value: '$sent',
|
||||
icon: Icons.local_hospital_outlined,
|
||||
color: AppColors.accent,
|
||||
bgColor: AppColors.inProgressBg)
|
||||
.animate(delay: 120.ms)
|
||||
.fadeIn(duration: 350.ms)
|
||||
.slideY(begin: 0.2, end: 0);
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(child: c1),
|
||||
@@ -883,8 +1119,8 @@ class _StatCard extends StatelessWidget {
|
||||
Container(
|
||||
width: 44,
|
||||
height: 44,
|
||||
decoration:
|
||||
BoxDecoration(color: bgColor, borderRadius: BorderRadius.circular(12)),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor, borderRadius: BorderRadius.circular(12)),
|
||||
child: Icon(icon, color: color, size: 22),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
@@ -933,85 +1169,85 @@ class _JobCard extends StatelessWidget {
|
||||
button: true,
|
||||
excludeSemantics: true,
|
||||
child: Material(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
child: InkWell(
|
||||
onTap: () => context.push('/clinic/jobs/${job.id}'),
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: AppColors.border)),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 46,
|
||||
height: 46,
|
||||
decoration: BoxDecoration(
|
||||
color: statusBg, borderRadius: BorderRadius.circular(12)),
|
||||
child: Icon(Icons.work_outline_rounded,
|
||||
color: statusColor, size: 22),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(job.patientCode,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary)),
|
||||
const SizedBox(height: 2),
|
||||
Text(job.labName ?? 'Laboratuvar',
|
||||
style: const TextStyle(
|
||||
fontSize: 13, color: AppColors.textSecondary)),
|
||||
const SizedBox(height: 6),
|
||||
Wrap(
|
||||
spacing: 6,
|
||||
children: [
|
||||
_Tag(
|
||||
label: job.prostheticType.label,
|
||||
color: AppColors.inProgress,
|
||||
bg: AppColors.inProgressBg),
|
||||
_Tag(
|
||||
label: job.status.label,
|
||||
color: statusColor,
|
||||
bg: statusBg),
|
||||
],
|
||||
),
|
||||
],
|
||||
child: InkWell(
|
||||
onTap: () => context.push('/clinic/jobs/${job.id}'),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: AppColors.border)),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 46,
|
||||
height: 46,
|
||||
decoration: BoxDecoration(
|
||||
color: statusBg, borderRadius: BorderRadius.circular(12)),
|
||||
child: Icon(Icons.work_outline_rounded,
|
||||
color: statusColor, size: 22),
|
||||
),
|
||||
),
|
||||
if (dueText != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Icon(Icons.calendar_today_outlined,
|
||||
size: 13,
|
||||
color: isOverdue
|
||||
? AppColors.cancelled
|
||||
: AppColors.textMuted),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
dueText,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(job.patientCode,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary)),
|
||||
const SizedBox(height: 2),
|
||||
Text(job.labName ?? 'Laboratuvar',
|
||||
style: const TextStyle(
|
||||
fontSize: 13, color: AppColors.textSecondary)),
|
||||
const SizedBox(height: 6),
|
||||
Wrap(
|
||||
spacing: 6,
|
||||
children: [
|
||||
_Tag(
|
||||
label: job.prostheticType.label,
|
||||
color: AppColors.inProgress,
|
||||
bg: AppColors.inProgressBg),
|
||||
_Tag(
|
||||
label: job.status.label,
|
||||
color: statusColor,
|
||||
bg: statusBg),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (dueText != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Icon(Icons.calendar_today_outlined,
|
||||
size: 13,
|
||||
color: isOverdue
|
||||
? AppColors.cancelled
|
||||
: AppColors.textSecondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
: AppColors.textMuted),
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
dueText,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isOverdue
|
||||
? AppColors.cancelled
|
||||
: AppColors.textSecondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1096,8 +1332,7 @@ class _EmptyJobs extends StatelessWidget {
|
||||
const Text(
|
||||
'Yeni iş oluşturduğunuzda\nburada görünecek',
|
||||
textAlign: TextAlign.center,
|
||||
style:
|
||||
TextStyle(fontSize: 13, color: AppColors.textSecondary),
|
||||
style: TextStyle(fontSize: 13, color: AppColors.textSecondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1222,8 +1457,8 @@ class _ShimmerBoxState extends State<_ShimmerBox>
|
||||
height: widget.height,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(widget.radius),
|
||||
color: Color.lerp(const Color(0xFFE2E8F0),
|
||||
const Color(0xFFF1F5F9), _anim.value)),
|
||||
color: Color.lerp(
|
||||
const Color(0xFFE2E8F0), const Color(0xFFF1F5F9), _anim.value)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user