278 lines
8.8 KiB
Dart
278 lines
8.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:inou_app/design/inou_theme.dart';
|
|
import 'package:inou_app/design/inou_text.dart';
|
|
import 'package:inou_app/design/widgets/widgets.dart';
|
|
import 'models.dart';
|
|
import 'mock_data.dart';
|
|
|
|
class DashboardPage extends StatelessWidget {
|
|
const DashboardPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return InouAuthPage(
|
|
userName: 'Johan', // TODO: get from auth state
|
|
onProfileTap: () => context.go('/profile'),
|
|
onLogoutTap: () => context.go('/login'),
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 48),
|
|
child: Center(
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(maxWidth: InouTheme.maxWidth),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Header
|
|
Text('Dossiers', style: InouText.pageTitle),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Manage your health dossiers and those shared with you.',
|
|
style: InouText.intro,
|
|
),
|
|
const SizedBox(height: 32),
|
|
|
|
// Dossier grid
|
|
LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
final crossAxisCount = constraints.maxWidth > 900
|
|
? 3
|
|
: constraints.maxWidth > 600
|
|
? 2
|
|
: 1;
|
|
return GridView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: crossAxisCount,
|
|
mainAxisSpacing: 12,
|
|
crossAxisSpacing: 12,
|
|
childAspectRatio: 1.6,
|
|
),
|
|
itemCount: mockDossiers.length + 1, // +1 for add card
|
|
itemBuilder: (context, index) {
|
|
if (index == mockDossiers.length) {
|
|
return _AddDossierCard();
|
|
}
|
|
return _DossierCard(dossier: mockDossiers[index]);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
|
|
const SizedBox(height: 48),
|
|
const InouFooter(),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _DossierCard extends StatelessWidget {
|
|
final DossierSummary dossier;
|
|
|
|
const _DossierCard({required this.dossier});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return InkWell(
|
|
onTap: () => context.go('/dossier/${dossier.id}'),
|
|
borderRadius: InouTheme.borderRadiusLg,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: InouTheme.bgCard,
|
|
border: Border.all(color: InouTheme.border),
|
|
borderRadius: InouTheme.borderRadiusLg,
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Header row with name and edit
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
dossier.name,
|
|
style: InouText.h3.copyWith(fontSize: 20),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
if (dossier.canEdit)
|
|
IconButton(
|
|
icon: const Icon(Icons.edit_outlined, size: 18),
|
|
color: InouTheme.textMuted,
|
|
padding: EdgeInsets.zero,
|
|
constraints: const BoxConstraints(),
|
|
onPressed: () {
|
|
// TODO: navigate to edit
|
|
},
|
|
),
|
|
],
|
|
),
|
|
|
|
// Relation/role
|
|
const SizedBox(height: 4),
|
|
Row(
|
|
children: [
|
|
Text(
|
|
dossier.isSelf
|
|
? 'you'
|
|
: 'my role: ${dossier.relation ?? "Unknown"}',
|
|
style: InouText.bodySmall.copyWith(color: InouTheme.textMuted),
|
|
),
|
|
if (dossier.isCareReceiver) ...[
|
|
const SizedBox(width: 8),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: InouTheme.successLight,
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
child: Text(
|
|
'care',
|
|
style: InouText.bodySmall.copyWith(
|
|
color: InouTheme.success,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
|
|
// DOB & Sex
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
[
|
|
if (dossier.dateOfBirth != null) dossier.dateOfBirth,
|
|
if (dossier.sex != null) dossier.sex,
|
|
].join(' · '),
|
|
style: InouText.bodySmall.copyWith(color: InouTheme.textMuted),
|
|
),
|
|
|
|
const Spacer(),
|
|
|
|
// Stats row
|
|
if (dossier.stats.hasAnyData) ...[
|
|
Wrap(
|
|
spacing: 12,
|
|
runSpacing: 4,
|
|
children: [
|
|
if (dossier.stats.imaging > 0)
|
|
_StatChip(
|
|
'📷 ${dossier.stats.imaging} ${dossier.stats.imaging == 1 ? 'study' : 'studies'}',
|
|
),
|
|
if (dossier.stats.labs > 0)
|
|
_StatChip(
|
|
'🧪 ${dossier.stats.labs} ${dossier.stats.labs == 1 ? 'lab' : 'labs'}',
|
|
),
|
|
if (dossier.stats.genome) const _StatChip('🧬 genome'),
|
|
if (dossier.stats.documents > 0)
|
|
_StatChip(
|
|
'📄 ${dossier.stats.documents} ${dossier.stats.documents == 1 ? 'doc' : 'docs'}',
|
|
),
|
|
if (dossier.stats.medications > 0)
|
|
_StatChip('💊 ${dossier.stats.medications} meds'),
|
|
if (dossier.stats.supplements > 0)
|
|
_StatChip('🌿 ${dossier.stats.supplements} supps'),
|
|
],
|
|
),
|
|
] else ...[
|
|
Text(
|
|
'No data yet',
|
|
style: InouText.bodySmall.copyWith(color: InouTheme.textMuted),
|
|
),
|
|
],
|
|
|
|
const SizedBox(height: 12),
|
|
|
|
// View button
|
|
InouButton(
|
|
text: 'View',
|
|
size: ButtonSize.small,
|
|
onPressed: () => context.go('/dossier/${dossier.id}'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _StatChip extends StatelessWidget {
|
|
final String text;
|
|
|
|
const _StatChip(this.text);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Text(
|
|
text,
|
|
style: InouText.bodySmall.copyWith(
|
|
color: InouTheme.textMuted,
|
|
fontSize: 12,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _AddDossierCard extends StatelessWidget {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return InkWell(
|
|
onTap: () {
|
|
// TODO: navigate to add dossier
|
|
},
|
|
borderRadius: InouTheme.borderRadiusLg,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
border: Border.all(
|
|
color: InouTheme.border,
|
|
width: 2,
|
|
style: BorderStyle.solid,
|
|
),
|
|
borderRadius: InouTheme.borderRadiusLg,
|
|
),
|
|
child: CustomPaint(
|
|
painter: _DashedBorderPainter(),
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
'+',
|
|
style: TextStyle(
|
|
fontSize: 28,
|
|
fontWeight: FontWeight.w300,
|
|
color: InouTheme.accent,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'Add dossier',
|
|
style: InouText.body.copyWith(color: InouTheme.textMuted),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _DashedBorderPainter extends CustomPainter {
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
// Empty - we use the container border for now
|
|
// Could implement dashed border if needed
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
|
}
|