pos changes

This commit is contained in:
2026-08-06 19:26:53 +05:30
parent cb065a0f69
commit eebd10da6d
42 changed files with 3451 additions and 2531 deletions

View File

@@ -35,6 +35,11 @@ import 'pos_view.dart';
/// * `11201300` sidebar as an icon rail, docked bill
/// * `9201120` icon rail, bill becomes a bottom sheet
/// * `< 920` sidebar goes off-canvas behind a menu button
///
/// In cashier mode the sidebar is not rendered at any width. That session has
/// exactly two destinations, and both are reachable from the header — a rail
/// holding one live tile is chrome for its own sake. Sign-out and the sync log
/// move up with it, since the sidebar was the only place they lived.
class PosDashboardScreen extends ConsumerStatefulWidget {
const PosDashboardScreen({super.key});
@@ -119,10 +124,15 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
@override
Widget build(BuildContext context) {
final layout = PosLayout.of(context);
final module = ref.watch(activeModuleProvider);
// Clamped, not raw: a module the admin left active must not carry into a
// cashier session.
final module = ref.watch(resolvedModuleProvider);
final ready = ref.watch(catalogueReadyProvider);
final isPos = module == PosModule.pos && ready;
final cashierMode = ref.watch(isCashierModeProvider);
final showSidebar = !cashierMode;
// Only the terminal itself needs the bill docked beside it.
final showDockedBill = isPos && !layout.billingIsSheet;
final showCartFab = isPos && layout.billingIsSheet;
@@ -130,7 +140,7 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
return Scaffold(
key: _scaffoldKey,
backgroundColor: AppColors.background,
drawer: layout.sidebarIsDrawer
drawer: showSidebar && layout.sidebarIsDrawer
? Drawer(
width: PosLayout.expandedWidth,
backgroundColor: AppColors.surface,
@@ -157,13 +167,18 @@ class _PosDashboardScreenState extends ConsumerState<PosDashboardScreen> {
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (!layout.sidebarIsDrawer) AppSidebar(mode: layout.sidebar),
if (showSidebar && !layout.sidebarIsDrawer)
AppSidebar(mode: layout.sidebar),
Expanded(
child: Column(
children: [
PageHeader(
layout: layout,
onMenuTap: () => _scaffoldKey.currentState?.openDrawer(),
// Nothing to open in cashier mode, so the button is not
// offered rather than opening an empty drawer.
onMenuTap: showSidebar
? () => _scaffoldKey.currentState?.openDrawer()
: null,
),
Expanded(
child: AnimatedSwitcher(

View File

@@ -5,6 +5,7 @@ import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/theme/app_layout.dart';
import '../../../core/widgets/primary_button.dart';
import '../../auth/providers/auth_controller.dart';
import '../../sync/providers/sync_controller.dart';
import '../providers/navigation_provider.dart';
import '../widgets/category_chips.dart';
@@ -82,6 +83,11 @@ class _CatalogueRequired extends ConsumerWidget {
final state = ref.watch(catalogueImportProvider);
final running = state is ImportRunning;
// Pulling the catalogue is an admin job, and a cashier has no Product
// Import module to be sent to. Offering them a button that opens a screen
// they cannot reach is worse than telling them who to ask.
final cashier = ref.watch(isCashierModeProvider);
return Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(AppSpacing.xxl),
@@ -102,17 +108,23 @@ class _CatalogueRequired extends ConsumerWidget {
),
const SizedBox(height: AppSpacing.xxl),
Text(
'Import products to start billing',
cashier
? 'No products on this terminal'
: 'Import products to start billing',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: AppSpacing.sm),
const Text(
'This terminal has no catalogue yet. Pull the current products '
'once at the start of your shift — after that everything runs '
'offline.',
Text(
cashier
? 'Nothing has been imported for this shift yet. Ask an '
'admin to sign in and pull the catalogue — once they '
'have, sign in again and everything runs offline.'
: 'This terminal has no catalogue yet. Pull the current '
'products once at the start of your shift — after that '
'everything runs offline.',
textAlign: TextAlign.center,
style: TextStyle(
style: const TextStyle(
fontSize: 14,
color: AppColors.textSecondary,
height: 1.6,
@@ -120,7 +132,7 @@ class _CatalogueRequired extends ConsumerWidget {
),
const SizedBox(height: AppSpacing.xxl),
if (running) ...[
if (!cashier && state is ImportRunning) ...[
Text(
state.stage,
style: const TextStyle(
@@ -142,7 +154,7 @@ class _CatalogueRequired extends ConsumerWidget {
const SizedBox(height: AppSpacing.lg),
],
if (state is ImportFailed) ...[
if (!cashier && state is ImportFailed) ...[
Container(
padding: const EdgeInsets.all(AppSpacing.md),
margin: const EdgeInsets.only(bottom: AppSpacing.lg),
@@ -171,23 +183,52 @@ class _CatalogueRequired extends ConsumerWidget {
),
],
PrimaryButton(
label: 'Import catalogue now',
icon: Icons.cloud_download_rounded,
large: true,
busy: running,
onPressed: running
? null
: () => ref.read(catalogueImportProvider.notifier).run(),
),
const SizedBox(height: AppSpacing.md),
TextButton.icon(
onPressed: () => ref
.read(activeModuleProvider.notifier)
.state = PosModule.productImport,
icon: const Icon(Icons.open_in_new_rounded, size: 16),
label: const Text('Open Product Import'),
),
if (cashier)
Container(
padding: const EdgeInsets.all(AppSpacing.md),
decoration: const BoxDecoration(
color: AppColors.infoSurface,
borderRadius: AppRadius.brSm,
),
child: const Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.admin_panel_settings_outlined,
size: 18, color: AppColors.info,),
SizedBox(width: AppSpacing.sm),
Expanded(
child: Text(
'Importing the catalogue is an admin job. Nothing '
'can be billed here until it has been done.',
style: TextStyle(
color: AppColors.info,
fontSize: 13,
height: 1.45,
),
),
),
],
),
)
else ...[
PrimaryButton(
label: 'Import catalogue now',
icon: Icons.cloud_download_rounded,
large: true,
busy: running,
onPressed: running
? null
: () => ref.read(catalogueImportProvider.notifier).run(),
),
const SizedBox(height: AppSpacing.md),
TextButton.icon(
onPressed: () => ref
.read(activeModuleProvider.notifier)
.state = PosModule.productImport,
icon: const Icon(Icons.open_in_new_rounded, size: 16),
label: const Text('Open Product Import'),
),
],
],
),
),