added product import and billing integration

This commit is contained in:
2026-08-05 18:15:21 +05:30
parent 33b4337933
commit 6c0266c9c7
32 changed files with 889 additions and 457 deletions

View File

@@ -1,10 +1,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../app/providers.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/utils/formatters.dart';
import '../../../core/widgets/primary_button.dart';
import '../../../data/sync/sync_engine.dart';
import '../../../domain/entities/transaction.dart';
import '../../../domain/repositories/sync_repository.dart';
import '../../sync/providers/sync_controller.dart';
@@ -26,10 +28,21 @@ class EventsView extends ConsumerWidget {
final syncState = ref.watch(orderSyncProvider);
final events = ref.watch(syncEventsProvider);
// The engine may not have emitted yet on a cold start, so fall back to
// its current value rather than showing nothing. This is the same state
// that drives the header pill — it is what actually knows whether the
// automatic push right after a sale succeeded, not just what the manual
// "Sync" button on this page last did.
final engine = ref.watch(syncEngineStateProvider).value ??
ref.watch(syncEngineProvider).state;
final r = report.value;
return ModulePage(
children: [
if (engine.lastError != null && pending > 0)
_EngineWarningBanner(engine: engine),
Wrap(
spacing: AppSpacing.lg,
runSpacing: AppSpacing.lg,
@@ -310,3 +323,81 @@ class EventsView extends ConsumerWidget {
),
);
}
/// Flags a bill that could not reach the server on its own — the automatic
/// push right after checkout, not the manual "Sync" button below.
///
/// Sits above everything else on the page because a bill stuck here is the
/// one thing on this screen that needs a person to notice it, rather than
/// just waiting for the next background retry.
class _EngineWarningBanner extends StatelessWidget {
const _EngineWarningBanner({required this.engine});
final SyncEngineState engine;
@override
Widget build(BuildContext context) {
final halted = engine.isHalted;
final color = halted ? AppColors.danger : AppColors.warning;
final surface = halted ? AppColors.dangerSurface : AppColors.warningSurface;
final retry = engine.nextAttemptAt;
final retryNote = halted
? 'Retrying will not help until this is fixed — press Sync below '
'once it is sorted.'
: retry == null
? 'It will retry automatically, or press Sync below to try now.'
: 'It will retry automatically at ${Formatters.time(retry)}, or '
'press Sync below to try now.';
return Container(
margin: const EdgeInsets.only(bottom: AppSpacing.lg),
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(color: surface, borderRadius: AppRadius.brLg),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(Icons.wifi_off_rounded, size: 20, color: color),
const SizedBox(width: AppSpacing.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
halted
? 'Sync halted — ${engine.pending} bill(s) not sent'
: "Couldn't reach the server — "
'${engine.pending} bill(s) not sent',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: color,
),
),
const SizedBox(height: 2),
Text(
engine.lastError ?? 'The last upload attempt failed.',
style: TextStyle(
fontSize: 12.5,
color: color,
height: 1.45,
),
),
const SizedBox(height: 4),
Text(
'$retryNote Every bill is still safe on this terminal — '
'nothing is lost while it waits.',
style: const TextStyle(
fontSize: 12,
color: AppColors.textSecondary,
height: 1.45,
),
),
],
),
),
],
),
);
}
}