Add pricing entry flow and platform admin foundations
This commit is contained in:
+282
-75
@@ -10,6 +10,7 @@ import '../../models/tenant.dart';
|
||||
import '../../features/auth/sign_in_screen.dart';
|
||||
import '../../features/auth/sign_up_screen.dart';
|
||||
import '../../features/auth/onboarding_screen.dart';
|
||||
import '../../features/auth/welcome_pricing_screen.dart';
|
||||
import '../../features/clinic/dashboard/clinic_dashboard_screen.dart';
|
||||
import '../../features/clinic/jobs/clinic_jobs_screen.dart';
|
||||
import '../../features/clinic/jobs/clinic_job_detail_screen.dart';
|
||||
@@ -37,6 +38,7 @@ import '../../models/connection.dart';
|
||||
const routeSignIn = '/sign-in';
|
||||
const routeSignUp = '/sign-up';
|
||||
const routeOnboarding = '/onboarding';
|
||||
const routeWelcome = '/welcome';
|
||||
|
||||
// Clinic routes
|
||||
const routeClinicDashboard = '/clinic/dashboard';
|
||||
@@ -65,15 +67,20 @@ const routeLabAi = '/lab/ai';
|
||||
const routeLabDiscounts = '/lab/discounts';
|
||||
|
||||
List<RouteBase> buildRoutes() => [
|
||||
GoRoute(
|
||||
path: routeWelcome, builder: (_, __) => const WelcomePricingScreen()),
|
||||
GoRoute(path: routeSignIn, builder: (_, __) => const SignInScreen()),
|
||||
GoRoute(path: routeSignUp, builder: (_, __) => const SignUpScreen()),
|
||||
GoRoute(path: routeOnboarding, builder: (_, __) => const OnboardingScreen()),
|
||||
GoRoute(
|
||||
path: routeOnboarding, builder: (_, __) => const OnboardingScreen()),
|
||||
|
||||
// ── Clinic shell ──────────────────────────────────────────────────────
|
||||
ShellRoute(
|
||||
builder: (context, state, child) => _ClinicShell(child: child),
|
||||
routes: [
|
||||
GoRoute(path: routeClinicDashboard, builder: (_, __) => const ClinicDashboardScreen()),
|
||||
GoRoute(
|
||||
path: routeClinicDashboard,
|
||||
builder: (_, __) => const ClinicDashboardScreen()),
|
||||
GoRoute(
|
||||
path: routeClinicJobs,
|
||||
builder: (_, __) => const ClinicJobsScreen(),
|
||||
@@ -81,7 +88,8 @@ List<RouteBase> buildRoutes() => [
|
||||
GoRoute(path: 'new', builder: (_, __) => const NewJobScreen()),
|
||||
GoRoute(
|
||||
path: ':jobId',
|
||||
builder: (_, s) => ClinicJobDetailScreen(jobId: s.pathParameters['jobId']!),
|
||||
builder: (_, s) =>
|
||||
ClinicJobDetailScreen(jobId: s.pathParameters['jobId']!),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -91,15 +99,25 @@ List<RouteBase> buildRoutes() => [
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ':patientId',
|
||||
builder: (_, s) => ClinicPatientDetailScreen(patientId: s.pathParameters['patientId']!),
|
||||
builder: (_, s) => ClinicPatientDetailScreen(
|
||||
patientId: s.pathParameters['patientId']!),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(path: routeClinicConnections, builder: (_, __) => const ClinicConnectionsScreen()),
|
||||
GoRoute(path: routeClinicFinance, builder: (_, __) => const ClinicFinanceScreen()),
|
||||
GoRoute(path: routeClinicSettings, builder: (_, __) => const ClinicSettingsScreen()),
|
||||
GoRoute(path: routeClinicReports, builder: (_, __) => const ReportsScreen()),
|
||||
GoRoute(path: routeClinicAi, builder: (_, __) => const AiChatScreen()),
|
||||
GoRoute(
|
||||
path: routeClinicConnections,
|
||||
builder: (_, __) => const ClinicConnectionsScreen()),
|
||||
GoRoute(
|
||||
path: routeClinicFinance,
|
||||
builder: (_, __) => const ClinicFinanceScreen()),
|
||||
GoRoute(
|
||||
path: routeClinicSettings,
|
||||
builder: (_, __) => const ClinicSettingsScreen()),
|
||||
GoRoute(
|
||||
path: routeClinicReports,
|
||||
builder: (_, __) => const ReportsScreen()),
|
||||
GoRoute(
|
||||
path: routeClinicAi, builder: (_, __) => const AiChatScreen()),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -107,19 +125,26 @@ List<RouteBase> buildRoutes() => [
|
||||
ShellRoute(
|
||||
builder: (context, state, child) => _LabShell(child: child),
|
||||
routes: [
|
||||
GoRoute(path: routeLabDashboard, builder: (_, __) => const LabDashboardScreen()),
|
||||
GoRoute(path: routeLabJobsInbound, builder: (_, __) => const LabJobsInboundScreen()),
|
||||
GoRoute(
|
||||
path: routeLabDashboard,
|
||||
builder: (_, __) => const LabDashboardScreen()),
|
||||
GoRoute(
|
||||
path: routeLabJobsInbound,
|
||||
builder: (_, __) => const LabJobsInboundScreen()),
|
||||
GoRoute(
|
||||
path: routeLabJobsAll,
|
||||
builder: (_, __) => const LabAllJobsScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ':jobId',
|
||||
builder: (_, s) => LabJobDetailScreen(jobId: s.pathParameters['jobId']!),
|
||||
builder: (_, s) =>
|
||||
LabJobDetailScreen(jobId: s.pathParameters['jobId']!),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(path: routeLabProducts, builder: (_, __) => const LabProductsScreen()),
|
||||
GoRoute(
|
||||
path: routeLabProducts,
|
||||
builder: (_, __) => const LabProductsScreen()),
|
||||
GoRoute(
|
||||
path: routeLabConnections,
|
||||
builder: (_, __) => const LabConnectionsScreen(),
|
||||
@@ -141,10 +166,17 @@ List<RouteBase> buildRoutes() => [
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(path: routeLabDiscounts, builder: (_, __) => const DiscountsScreen()),
|
||||
GoRoute(path: routeLabFinance, builder: (_, __) => const LabFinanceScreen()),
|
||||
GoRoute(path: routeLabSettings, builder: (_, __) => const LabSettingsScreen()),
|
||||
GoRoute(path: routeLabReports, builder: (_, __) => const ReportsScreen()),
|
||||
GoRoute(
|
||||
path: routeLabDiscounts,
|
||||
builder: (_, __) => const DiscountsScreen()),
|
||||
GoRoute(
|
||||
path: routeLabFinance,
|
||||
builder: (_, __) => const LabFinanceScreen()),
|
||||
GoRoute(
|
||||
path: routeLabSettings,
|
||||
builder: (_, __) => const LabSettingsScreen()),
|
||||
GoRoute(
|
||||
path: routeLabReports, builder: (_, __) => const ReportsScreen()),
|
||||
GoRoute(path: routeLabAi, builder: (_, __) => const AiChatScreen()),
|
||||
],
|
||||
),
|
||||
@@ -216,11 +248,36 @@ class _ClinicShellState extends ConsumerState<_ClinicShell> {
|
||||
String _selectedRoute = routeClinicDashboard;
|
||||
|
||||
List<_NavItem> _clinicTopSingles(AppStrings s) => [
|
||||
_NavItem(route: routeClinicDashboard, icon: const Icon(Icons.home_outlined), selectedIcon: const Icon(Icons.home_rounded), label: s.homeTitle, visible: (_) => true),
|
||||
_NavItem(route: routeClinicJobs, icon: const Icon(Icons.work_outline_rounded), selectedIcon: const Icon(Icons.work_rounded), label: s.jobsTitle, visible: (m) => m?.showJobs ?? true),
|
||||
_NavItem(route: routeClinicPatients, icon: const Icon(Icons.people_outline_rounded), selectedIcon: const Icon(Icons.people_rounded), label: s.patientsTitle, visible: (m) => m?.showPatients ?? true),
|
||||
_NavItem(route: routeClinicFinance, icon: const Icon(Icons.account_balance_outlined), selectedIcon: const Icon(Icons.account_balance_rounded), label: s.finance, visible: (m) => m?.showFinance ?? true),
|
||||
_NavItem(route: routeClinicAi, icon: const Icon(Icons.auto_awesome_outlined), selectedIcon: const Icon(Icons.auto_awesome_rounded), label: s.aiAssistant, visible: (_) => true),
|
||||
_NavItem(
|
||||
route: routeClinicDashboard,
|
||||
icon: const Icon(Icons.home_outlined),
|
||||
selectedIcon: const Icon(Icons.home_rounded),
|
||||
label: s.homeTitle,
|
||||
visible: (_) => true),
|
||||
_NavItem(
|
||||
route: routeClinicJobs,
|
||||
icon: const Icon(Icons.work_outline_rounded),
|
||||
selectedIcon: const Icon(Icons.work_rounded),
|
||||
label: s.jobsTitle,
|
||||
visible: (m) => m?.showJobs ?? true),
|
||||
_NavItem(
|
||||
route: routeClinicPatients,
|
||||
icon: const Icon(Icons.people_outline_rounded),
|
||||
selectedIcon: const Icon(Icons.people_rounded),
|
||||
label: s.patientsTitle,
|
||||
visible: (m) => m?.showPatients ?? true),
|
||||
_NavItem(
|
||||
route: routeClinicFinance,
|
||||
icon: const Icon(Icons.account_balance_outlined),
|
||||
selectedIcon: const Icon(Icons.account_balance_rounded),
|
||||
label: s.finance,
|
||||
visible: (m) => m?.showFinance ?? true),
|
||||
_NavItem(
|
||||
route: routeClinicAi,
|
||||
icon: const Icon(Icons.auto_awesome_outlined),
|
||||
selectedIcon: const Icon(Icons.auto_awesome_rounded),
|
||||
label: s.aiAssistant,
|
||||
visible: (_) => true),
|
||||
];
|
||||
|
||||
List<_NavGroup> _clinicGroups(AppStrings s) => [
|
||||
@@ -229,22 +286,62 @@ class _ClinicShellState extends ConsumerState<_ClinicShell> {
|
||||
icon: Icons.tune_rounded,
|
||||
selectedIcon: Icons.tune_rounded,
|
||||
items: [
|
||||
_NavItem(route: routeClinicConnections, icon: const Icon(Icons.link_rounded), selectedIcon: const Icon(Icons.link_rounded), label: s.connections, visible: (_) => true),
|
||||
_NavItem(route: routeClinicReports, icon: const Icon(Icons.bar_chart_outlined), selectedIcon: const Icon(Icons.bar_chart_rounded), label: s.reports, visible: (_) => true),
|
||||
_NavItem(
|
||||
route: routeClinicConnections,
|
||||
icon: const Icon(Icons.link_rounded),
|
||||
selectedIcon: const Icon(Icons.link_rounded),
|
||||
label: s.connections,
|
||||
visible: (_) => true),
|
||||
_NavItem(
|
||||
route: routeClinicReports,
|
||||
icon: const Icon(Icons.bar_chart_outlined),
|
||||
selectedIcon: const Icon(Icons.bar_chart_rounded),
|
||||
label: s.reports,
|
||||
visible: (_) => true),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
List<_NavItem> _clinicBottomSingles(AppStrings s) => [
|
||||
_NavItem(route: routeClinicSettings, icon: const Icon(Icons.settings_outlined), selectedIcon: const Icon(Icons.settings_rounded), label: s.settings, visible: (_) => true),
|
||||
_NavItem(
|
||||
route: routeClinicSettings,
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
selectedIcon: const Icon(Icons.settings_rounded),
|
||||
label: s.settings,
|
||||
visible: (_) => true),
|
||||
];
|
||||
|
||||
List<_NavItem> _clinicMobileItems(AppStrings s) => [
|
||||
_NavItem(route: routeClinicDashboard, icon: const Icon(Icons.home_outlined), selectedIcon: const Icon(Icons.home_rounded), label: s.homeTitle, visible: (_) => true),
|
||||
_NavItem(route: routeClinicJobs, icon: const Icon(Icons.work_outline_rounded), selectedIcon: const Icon(Icons.work_rounded), label: s.jobsTitle, visible: (m) => m?.showJobs ?? true),
|
||||
_NavItem(route: routeClinicPatients, icon: const Icon(Icons.people_outline_rounded), selectedIcon: const Icon(Icons.people_rounded), label: s.patientsTitle, visible: (m) => m?.showPatients ?? true),
|
||||
_NavItem(route: routeClinicFinance, icon: const Icon(Icons.account_balance_outlined), selectedIcon: const Icon(Icons.account_balance_rounded), label: s.finance, visible: (m) => m?.showFinance ?? true),
|
||||
_NavItem(route: routeClinicSettings, icon: const Icon(Icons.settings_outlined), selectedIcon: const Icon(Icons.settings_rounded), label: s.settings, visible: (_) => true),
|
||||
_NavItem(
|
||||
route: routeClinicDashboard,
|
||||
icon: const Icon(Icons.home_outlined),
|
||||
selectedIcon: const Icon(Icons.home_rounded),
|
||||
label: s.homeTitle,
|
||||
visible: (_) => true),
|
||||
_NavItem(
|
||||
route: routeClinicJobs,
|
||||
icon: const Icon(Icons.work_outline_rounded),
|
||||
selectedIcon: const Icon(Icons.work_rounded),
|
||||
label: s.jobsTitle,
|
||||
visible: (m) => m?.showJobs ?? true),
|
||||
_NavItem(
|
||||
route: routeClinicPatients,
|
||||
icon: const Icon(Icons.people_outline_rounded),
|
||||
selectedIcon: const Icon(Icons.people_rounded),
|
||||
label: s.patientsTitle,
|
||||
visible: (m) => m?.showPatients ?? true),
|
||||
_NavItem(
|
||||
route: routeClinicFinance,
|
||||
icon: const Icon(Icons.account_balance_outlined),
|
||||
selectedIcon: const Icon(Icons.account_balance_rounded),
|
||||
label: s.finance,
|
||||
visible: (m) => m?.showFinance ?? true),
|
||||
_NavItem(
|
||||
route: routeClinicSettings,
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
selectedIcon: const Icon(Icons.settings_rounded),
|
||||
label: s.settings,
|
||||
visible: (_) => true),
|
||||
];
|
||||
|
||||
List<_SidebarEntry> _allEntries(AppStrings s) {
|
||||
@@ -265,7 +362,8 @@ class _ClinicShellState extends ConsumerState<_ClinicShell> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = ref.watch(stringsProvider);
|
||||
final isDesktop = MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint;
|
||||
final isDesktop =
|
||||
MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint;
|
||||
|
||||
if (isDesktop) {
|
||||
final entries = _allEntries(s);
|
||||
@@ -289,7 +387,8 @@ class _ClinicShellState extends ConsumerState<_ClinicShell> {
|
||||
|
||||
// Mobile: only core items in bottom nav
|
||||
final membership = ref.read(authProvider).activeTenant;
|
||||
final items = _clinicMobileItems(s).where((it) => it.visible(membership)).toList();
|
||||
final items =
|
||||
_clinicMobileItems(s).where((it) => it.visible(membership)).toList();
|
||||
final flatIndex = items.indexWhere((it) => it.route == _selectedRoute);
|
||||
final clampedIndex = flatIndex >= 0 ? flatIndex : 0;
|
||||
|
||||
@@ -317,7 +416,10 @@ class _ClinicShellState extends ConsumerState<_ClinicShell> {
|
||||
Semantics(
|
||||
label: it.label,
|
||||
button: true,
|
||||
child: NavigationDestination(icon: it.icon, selectedIcon: it.selectedIcon, label: it.label),
|
||||
child: NavigationDestination(
|
||||
icon: it.icon,
|
||||
selectedIcon: it.selectedIcon,
|
||||
label: it.label),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -339,11 +441,36 @@ class _LabShellState extends ConsumerState<_LabShell> {
|
||||
String _selectedRoute = routeLabDashboard;
|
||||
|
||||
List<_NavItem> _labTopSingles(AppStrings s) => [
|
||||
_NavItem(route: routeLabDashboard, icon: const Icon(Icons.home_outlined), selectedIcon: const Icon(Icons.home_rounded), label: s.homeTitle, visible: (_) => true),
|
||||
_NavItem(route: routeLabJobsAll, icon: const Icon(Icons.work_outline_rounded), selectedIcon: const Icon(Icons.work_rounded), label: s.jobsTitle, visible: (m) => m?.showJobs ?? true),
|
||||
_NavItem(route: routeLabProducts, icon: const Icon(Icons.inventory_2_outlined), selectedIcon: const Icon(Icons.inventory_2_rounded), label: s.productsTitle, visible: (m) => m?.showProducts ?? true),
|
||||
_NavItem(route: routeLabFinance, icon: const Icon(Icons.account_balance_outlined), selectedIcon: const Icon(Icons.account_balance_rounded), label: s.finance, visible: (m) => m?.showFinance ?? true),
|
||||
_NavItem(route: routeLabAi, icon: const Icon(Icons.auto_awesome_outlined), selectedIcon: const Icon(Icons.auto_awesome_rounded), label: s.aiAssistant, visible: (_) => true),
|
||||
_NavItem(
|
||||
route: routeLabDashboard,
|
||||
icon: const Icon(Icons.home_outlined),
|
||||
selectedIcon: const Icon(Icons.home_rounded),
|
||||
label: s.homeTitle,
|
||||
visible: (_) => true),
|
||||
_NavItem(
|
||||
route: routeLabJobsAll,
|
||||
icon: const Icon(Icons.work_outline_rounded),
|
||||
selectedIcon: const Icon(Icons.work_rounded),
|
||||
label: s.jobsTitle,
|
||||
visible: (m) => m?.showJobs ?? true),
|
||||
_NavItem(
|
||||
route: routeLabProducts,
|
||||
icon: const Icon(Icons.inventory_2_outlined),
|
||||
selectedIcon: const Icon(Icons.inventory_2_rounded),
|
||||
label: s.productsTitle,
|
||||
visible: (m) => m?.showProducts ?? true),
|
||||
_NavItem(
|
||||
route: routeLabFinance,
|
||||
icon: const Icon(Icons.account_balance_outlined),
|
||||
selectedIcon: const Icon(Icons.account_balance_rounded),
|
||||
label: s.finance,
|
||||
visible: (m) => m?.showFinance ?? true),
|
||||
_NavItem(
|
||||
route: routeLabAi,
|
||||
icon: const Icon(Icons.auto_awesome_outlined),
|
||||
selectedIcon: const Icon(Icons.auto_awesome_rounded),
|
||||
label: s.aiAssistant,
|
||||
visible: (_) => true),
|
||||
];
|
||||
|
||||
List<_NavGroup> _labGroups(AppStrings s) => [
|
||||
@@ -352,23 +479,68 @@ class _LabShellState extends ConsumerState<_LabShell> {
|
||||
icon: Icons.tune_rounded,
|
||||
selectedIcon: Icons.tune_rounded,
|
||||
items: [
|
||||
_NavItem(route: routeLabConnections, icon: const Icon(Icons.link_rounded), selectedIcon: const Icon(Icons.link_rounded), label: s.connections, visible: (_) => true),
|
||||
_NavItem(route: routeLabDiscounts, icon: const Icon(Icons.local_offer_outlined), selectedIcon: const Icon(Icons.local_offer_rounded), label: s.discounts, visible: (_) => true),
|
||||
_NavItem(route: routeLabReports, icon: const Icon(Icons.bar_chart_outlined), selectedIcon: const Icon(Icons.bar_chart_rounded), label: s.reports, visible: (_) => true),
|
||||
_NavItem(
|
||||
route: routeLabConnections,
|
||||
icon: const Icon(Icons.link_rounded),
|
||||
selectedIcon: const Icon(Icons.link_rounded),
|
||||
label: s.connections,
|
||||
visible: (_) => true),
|
||||
_NavItem(
|
||||
route: routeLabDiscounts,
|
||||
icon: const Icon(Icons.local_offer_outlined),
|
||||
selectedIcon: const Icon(Icons.local_offer_rounded),
|
||||
label: s.discounts,
|
||||
visible: (_) => true),
|
||||
_NavItem(
|
||||
route: routeLabReports,
|
||||
icon: const Icon(Icons.bar_chart_outlined),
|
||||
selectedIcon: const Icon(Icons.bar_chart_rounded),
|
||||
label: s.reports,
|
||||
visible: (_) => true),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
List<_NavItem> _labBottomSingles(AppStrings s) => [
|
||||
_NavItem(route: routeLabSettings, icon: const Icon(Icons.settings_outlined), selectedIcon: const Icon(Icons.settings_rounded), label: s.settings, visible: (_) => true),
|
||||
_NavItem(
|
||||
route: routeLabSettings,
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
selectedIcon: const Icon(Icons.settings_rounded),
|
||||
label: s.settings,
|
||||
visible: (_) => true),
|
||||
];
|
||||
|
||||
List<_NavItem> _labMobileItems(AppStrings s) => [
|
||||
_NavItem(route: routeLabDashboard, icon: const Icon(Icons.home_outlined), selectedIcon: const Icon(Icons.home_rounded), label: s.homeTitle, visible: (_) => true),
|
||||
_NavItem(route: routeLabJobsAll, icon: const Icon(Icons.work_outline_rounded), selectedIcon: const Icon(Icons.work_rounded), label: s.jobsTitle, visible: (m) => m?.showJobs ?? true),
|
||||
_NavItem(route: routeLabProducts, icon: const Icon(Icons.inventory_2_outlined), selectedIcon: const Icon(Icons.inventory_2_rounded), label: s.productsTitle, visible: (m) => m?.showProducts ?? true),
|
||||
_NavItem(route: routeLabFinance, icon: const Icon(Icons.account_balance_outlined), selectedIcon: const Icon(Icons.account_balance_rounded), label: s.finance, visible: (m) => m?.showFinance ?? true),
|
||||
_NavItem(route: routeLabSettings, icon: const Icon(Icons.settings_outlined), selectedIcon: const Icon(Icons.settings_rounded), label: s.settings, visible: (_) => true),
|
||||
_NavItem(
|
||||
route: routeLabDashboard,
|
||||
icon: const Icon(Icons.home_outlined),
|
||||
selectedIcon: const Icon(Icons.home_rounded),
|
||||
label: s.homeTitle,
|
||||
visible: (_) => true),
|
||||
_NavItem(
|
||||
route: routeLabJobsAll,
|
||||
icon: const Icon(Icons.work_outline_rounded),
|
||||
selectedIcon: const Icon(Icons.work_rounded),
|
||||
label: s.jobsTitle,
|
||||
visible: (m) => m?.showJobs ?? true),
|
||||
_NavItem(
|
||||
route: routeLabProducts,
|
||||
icon: const Icon(Icons.inventory_2_outlined),
|
||||
selectedIcon: const Icon(Icons.inventory_2_rounded),
|
||||
label: s.productsTitle,
|
||||
visible: (m) => m?.showProducts ?? true),
|
||||
_NavItem(
|
||||
route: routeLabFinance,
|
||||
icon: const Icon(Icons.account_balance_outlined),
|
||||
selectedIcon: const Icon(Icons.account_balance_rounded),
|
||||
label: s.finance,
|
||||
visible: (m) => m?.showFinance ?? true),
|
||||
_NavItem(
|
||||
route: routeLabSettings,
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
selectedIcon: const Icon(Icons.settings_rounded),
|
||||
label: s.settings,
|
||||
visible: (_) => true),
|
||||
];
|
||||
|
||||
List<_SidebarEntry> _allEntries(AppStrings s) {
|
||||
@@ -389,7 +561,8 @@ class _LabShellState extends ConsumerState<_LabShell> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = ref.watch(stringsProvider);
|
||||
final isDesktop = MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint;
|
||||
final isDesktop =
|
||||
MediaQuery.sizeOf(context).width > AppLayout.sidebarBreakpoint;
|
||||
|
||||
if (isDesktop) {
|
||||
final entries = _allEntries(s);
|
||||
@@ -413,7 +586,8 @@ class _LabShellState extends ConsumerState<_LabShell> {
|
||||
|
||||
// Mobile: only core items in bottom nav
|
||||
final membership = ref.read(authProvider).activeTenant;
|
||||
final items = _labMobileItems(s).where((it) => it.visible(membership)).toList();
|
||||
final items =
|
||||
_labMobileItems(s).where((it) => it.visible(membership)).toList();
|
||||
final flatIndex = items.indexWhere((it) => it.route == _selectedRoute);
|
||||
final clampedIndex = flatIndex >= 0 ? flatIndex : 0;
|
||||
|
||||
@@ -441,7 +615,10 @@ class _LabShellState extends ConsumerState<_LabShell> {
|
||||
Semantics(
|
||||
label: it.label,
|
||||
button: true,
|
||||
child: NavigationDestination(icon: it.icon, selectedIcon: it.selectedIcon, label: it.label),
|
||||
child: NavigationDestination(
|
||||
icon: it.icon,
|
||||
selectedIcon: it.selectedIcon,
|
||||
label: it.label),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -483,7 +660,10 @@ class _DesktopSidebarState extends State<_DesktopSidebar> {
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
border: Border(right: BorderSide(color: AppColors.border)),
|
||||
boxShadow: [BoxShadow(color: Color(0x08000000), blurRadius: 8, offset: Offset(2, 0))],
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Color(0x08000000), blurRadius: 8, offset: Offset(2, 0))
|
||||
],
|
||||
),
|
||||
child: ClipRect(
|
||||
child: Column(
|
||||
@@ -492,7 +672,8 @@ class _DesktopSidebarState extends State<_DesktopSidebar> {
|
||||
Container(
|
||||
height: _DesktopSidebar.headerHeight,
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(colors: [AppColors.primary, AppColors.accent]),
|
||||
gradient: LinearGradient(
|
||||
colors: [AppColors.primary, AppColors.accent]),
|
||||
border: Border(bottom: BorderSide(color: AppColors.border)),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
@@ -504,15 +685,21 @@ class _DesktopSidebarState extends State<_DesktopSidebar> {
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.25)),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.25)),
|
||||
),
|
||||
child: const Center(child: ToothLogo(size: 18, color: Colors.white)),
|
||||
child: const Center(
|
||||
child: ToothLogo(size: 18, color: Colors.white)),
|
||||
),
|
||||
if (_open) ...[
|
||||
const SizedBox(width: 10),
|
||||
const Text(
|
||||
'DLS',
|
||||
style: TextStyle(color: Colors.white, fontSize: 17, fontWeight: FontWeight.w800, letterSpacing: 1),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: 1),
|
||||
),
|
||||
],
|
||||
],
|
||||
@@ -525,8 +712,7 @@ class _DesktopSidebarState extends State<_DesktopSidebar> {
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
for (final entry in widget.entries)
|
||||
_buildEntry(entry),
|
||||
for (final entry in widget.entries) _buildEntry(entry),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
@@ -545,17 +731,24 @@ class _DesktopSidebarState extends State<_DesktopSidebar> {
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: Row(
|
||||
mainAxisAlignment: _open ? MainAxisAlignment.start : MainAxisAlignment.center,
|
||||
mainAxisAlignment: _open
|
||||
? MainAxisAlignment.start
|
||||
: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (_open) const SizedBox(width: 20),
|
||||
AnimatedRotation(
|
||||
duration: const Duration(milliseconds: 220),
|
||||
turns: _open ? 0.5 : 0,
|
||||
child: const Icon(Icons.chevron_right_rounded, color: AppColors.textMuted, size: 20),
|
||||
child: const Icon(Icons.chevron_right_rounded,
|
||||
color: AppColors.textMuted, size: 20),
|
||||
),
|
||||
if (_open) ...[
|
||||
const SizedBox(width: 8),
|
||||
const Text('Daralt', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: AppColors.textMuted)),
|
||||
const Text('Daralt',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.textMuted)),
|
||||
],
|
||||
],
|
||||
),
|
||||
@@ -629,7 +822,9 @@ class _SidebarItem extends StatelessWidget {
|
||||
children: [
|
||||
IconTheme(
|
||||
data: IconThemeData(
|
||||
color: selected ? AppColors.primary : AppColors.textSecondary,
|
||||
color: selected
|
||||
? AppColors.primary
|
||||
: AppColors.textSecondary,
|
||||
size: 20,
|
||||
),
|
||||
child: selected ? selectedIcon : icon,
|
||||
@@ -639,8 +834,11 @@ class _SidebarItem extends StatelessWidget {
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: selected ? FontWeight.w600 : FontWeight.w500,
|
||||
color: selected ? AppColors.primary : AppColors.textSecondary,
|
||||
fontWeight:
|
||||
selected ? FontWeight.w600 : FontWeight.w500,
|
||||
color: selected
|
||||
? AppColors.primary
|
||||
: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -649,7 +847,9 @@ class _SidebarItem extends StatelessWidget {
|
||||
: Center(
|
||||
child: IconTheme(
|
||||
data: IconThemeData(
|
||||
color: selected ? AppColors.primary : AppColors.textSecondary,
|
||||
color: selected
|
||||
? AppColors.primary
|
||||
: AppColors.textSecondary,
|
||||
size: 20,
|
||||
),
|
||||
child: selected ? selectedIcon : icon,
|
||||
@@ -740,7 +940,9 @@ class _SidebarGroupState extends State<_SidebarGroup> {
|
||||
child: Icon(
|
||||
isSelected ? widget.group.selectedIcon : widget.group.icon,
|
||||
size: 20,
|
||||
color: isSelected ? AppColors.primary : AppColors.textSecondary,
|
||||
color: isSelected
|
||||
? AppColors.primary
|
||||
: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -768,20 +970,24 @@ class _SidebarGroupState extends State<_SidebarGroup> {
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
isSelected ? widget.group.selectedIcon : widget.group.icon,
|
||||
isSelected
|
||||
? widget.group.selectedIcon
|
||||
: widget.group.icon,
|
||||
size: 20,
|
||||
color: isSelected ? AppColors.primary : AppColors.textSecondary,
|
||||
color: isSelected
|
||||
? AppColors.primary
|
||||
: AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.group.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
child: Text(
|
||||
widget.group.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedRotation(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
@@ -803,7 +1009,8 @@ class _SidebarGroupState extends State<_SidebarGroup> {
|
||||
// Sub-items (animated expand/collapse)
|
||||
AnimatedCrossFade(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
crossFadeState: _expanded ? CrossFadeState.showFirst : CrossFadeState.showSecond,
|
||||
crossFadeState:
|
||||
_expanded ? CrossFadeState.showFirst : CrossFadeState.showSecond,
|
||||
firstChild: Column(
|
||||
children: [
|
||||
for (final item in widget.group.items)
|
||||
|
||||
Reference in New Issue
Block a user