|
|
@@ -1,43 +1,487 @@
|
|
|
import 'package:flutter/material.dart';
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
-import 'package:ristoris/core/utils.dart';
|
|
|
-import 'package:ristoris/providers/database_provider.dart';
|
|
|
import 'package:go_router/go_router.dart';
|
|
|
+import 'package:intl/intl.dart';
|
|
|
+import 'package:ristoris/database/database.dart';
|
|
|
+import 'package:ristoris/providers/database_provider.dart';
|
|
|
+import 'package:ristoris/core/constants.dart';
|
|
|
+import 'package:ristoris/ui/widgets/order_actions.dart';
|
|
|
+
|
|
|
+String _statoLabel(String? stato) {
|
|
|
+ switch (stato) {
|
|
|
+ case AppConstants.kStatusNew:
|
|
|
+ return 'Nuovo';
|
|
|
+ case AppConstants.kStatusTransferred:
|
|
|
+ return 'Trasmesso';
|
|
|
+ case AppConstants.kStatoHold:
|
|
|
+ return 'Sospeso';
|
|
|
+ default:
|
|
|
+ return stato ?? '';
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+Color _statoColor(String? stato) {
|
|
|
+ switch (stato) {
|
|
|
+ case AppConstants.kStatusNew:
|
|
|
+ return Colors.orange;
|
|
|
+ case AppConstants.kStatusTransferred:
|
|
|
+ return Colors.green;
|
|
|
+ case AppConstants.kStatoHold:
|
|
|
+ return Colors.blue;
|
|
|
+ default:
|
|
|
+ return Colors.grey;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class _StatoPill extends StatelessWidget {
|
|
|
+ final String? stato;
|
|
|
+ const _StatoPill({this.stato});
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ final label = _statoLabel(stato);
|
|
|
+ final color = _statoColor(stato);
|
|
|
+
|
|
|
+ return Container(
|
|
|
+ padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: color.withAlpha(25),
|
|
|
+ borderRadius: BorderRadius.circular(4),
|
|
|
+ ),
|
|
|
+ child: Text(
|
|
|
+ label,
|
|
|
+ textAlign: TextAlign.center,
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 12,
|
|
|
+ height: 16 / 12,
|
|
|
+ fontWeight: FontWeight.w500,
|
|
|
+ color: color,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
class OrdersTab extends ConsumerWidget {
|
|
|
- final int idCliente;
|
|
|
+ final Cliente cliente;
|
|
|
final ThemeData theme;
|
|
|
- const OrdersTab({required this.idCliente, required this.theme});
|
|
|
+ const OrdersTab({required this.cliente, required this.theme, super.key});
|
|
|
|
|
|
@override
|
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
|
final ordiniDao = ref.read(ordiniDaoProvider);
|
|
|
+ final isPhone = MediaQuery.of(context).size.width < 600;
|
|
|
+
|
|
|
+ Future<void> deleteOrder(VOrdiniData o) async {
|
|
|
+ final dao = ref.read(ordiniDaoProvider);
|
|
|
+ await dao.zapOrderLines(o.idOrdine);
|
|
|
+ await dao.deleteOrder(o.idOrdine);
|
|
|
+ if (context.mounted) context.go('/dashboard/customers');
|
|
|
+ }
|
|
|
+
|
|
|
+ final blocco = cliente.codBlocco;
|
|
|
+ final isBloccato = blocco == '001';
|
|
|
+ final hasWarning =
|
|
|
+ blocco != null && blocco.isNotEmpty && blocco != '001';
|
|
|
+
|
|
|
return Scaffold(
|
|
|
body: SafeArea(
|
|
|
child: FutureBuilder(
|
|
|
- future: ordiniDao.customerOrdersWidthData(idCliente, false),
|
|
|
+ future: ordiniDao.customerOrdersWidthData(cliente.idCliente, false),
|
|
|
builder: (_, snap) {
|
|
|
if (!snap.hasData) {
|
|
|
return const Center(child: CircularProgressIndicator());
|
|
|
}
|
|
|
final orders = snap.data!;
|
|
|
- if (orders.isEmpty) return const Center(child: Text('Nessun ordine'));
|
|
|
- return ListView.builder(
|
|
|
- itemCount: orders.length,
|
|
|
- itemBuilder: (_, i) => ListTile(
|
|
|
- title: Text('Ordine #${orders[i].numeroOrdine ?? ""}'),
|
|
|
- subtitle: Text(Utils.formatDate(orders[i].dataOrdine) ?? ''),
|
|
|
- onTap: () => context.push('/dashboard/orders/${orders[i].idOrdine}'),
|
|
|
- ),
|
|
|
+ if (orders.isEmpty) {
|
|
|
+ return const Center(child: Text('Nessun ordine'));
|
|
|
+ }
|
|
|
+ if (isPhone) {
|
|
|
+ return ListView.builder(
|
|
|
+ padding: const EdgeInsets.all(16),
|
|
|
+ itemCount: orders.length,
|
|
|
+ itemBuilder: (_, i) => _OrderCard(
|
|
|
+ order: orders[i],
|
|
|
+ onEdit: () =>
|
|
|
+ context.push('/dashboard/orders/${orders[i].idOrdine}'),
|
|
|
+ onDelete: () => deleteOrder(orders[i]),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return _OrderTable(
|
|
|
+ orders: orders,
|
|
|
+ onEdit: (o) => context.push('/dashboard/orders/${o.idOrdine}'),
|
|
|
+ onDelete: deleteOrder,
|
|
|
);
|
|
|
},
|
|
|
),
|
|
|
),
|
|
|
- floatingActionButton: FloatingActionButton(
|
|
|
- heroTag: 'addOrder',
|
|
|
- child: const Icon(Icons.add),
|
|
|
- onPressed: () => context.push('/dashboard/orders/new?cliente=$idCliente'),
|
|
|
+ floatingActionButton: isBloccato
|
|
|
+ ? null
|
|
|
+ : FloatingActionButton(
|
|
|
+ heroTag: 'addOrder',
|
|
|
+ backgroundColor: hasWarning ? Colors.amber : null,
|
|
|
+ onPressed: () {
|
|
|
+ if (hasWarning) {
|
|
|
+ ScaffoldMessenger.of(context).showSnackBar(
|
|
|
+ SnackBar(
|
|
|
+ content: Text('Attenzione: cliente con blocco $blocco'),
|
|
|
+ backgroundColor: Colors.amber.shade800,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ context.push('/dashboard/orders/new?cliente=${cliente.idCliente}');
|
|
|
+ },
|
|
|
+ child: hasWarning
|
|
|
+ ? const Icon(Icons.warning_amber_rounded)
|
|
|
+ : const Icon(Icons.add),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class _OrderCard extends StatelessWidget {
|
|
|
+ final VOrdiniData order;
|
|
|
+ final VoidCallback onEdit;
|
|
|
+ final Future<void> Function() onDelete;
|
|
|
+ const _OrderCard({
|
|
|
+ required this.order,
|
|
|
+ required this.onEdit,
|
|
|
+ required this.onDelete,
|
|
|
+ });
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ final theme = Theme.of(context);
|
|
|
+ final cs = theme.colorScheme;
|
|
|
+ final dateFormat = DateFormat('dd/MM/yyyy');
|
|
|
+ final currencyFormat = NumberFormat.currency(
|
|
|
+ symbol: '€ ',
|
|
|
+ decimalDigits: 2,
|
|
|
+ );
|
|
|
+
|
|
|
+ return Card(
|
|
|
+ margin: const EdgeInsets.only(bottom: 12),
|
|
|
+ elevation: 0,
|
|
|
+ shape: RoundedRectangleBorder(
|
|
|
+ borderRadius: BorderRadius.circular(12),
|
|
|
+ side: BorderSide(color: cs.outlineVariant),
|
|
|
+ ),
|
|
|
+ child: InkWell(
|
|
|
+ onTap: onEdit,
|
|
|
+ borderRadius: BorderRadius.circular(12),
|
|
|
+ child: Padding(
|
|
|
+ padding: const EdgeInsets.all(16),
|
|
|
+ child: Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ Row(
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ child: Text(
|
|
|
+ '#${order.numeroOrdine ?? ''}',
|
|
|
+ style: theme.textTheme.titleMedium?.copyWith(
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ Text(
|
|
|
+ order.dataOrdine != null ? dateFormat.format(DateTime.parse(order.dataOrdine!)) : '',
|
|
|
+ style: theme.textTheme.bodyMedium?.copyWith(
|
|
|
+ color: cs.onSurfaceVariant,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 8),
|
|
|
+ _StatoPill(stato: order.stato),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 8),
|
|
|
+ Text(
|
|
|
+ '${order.filiale ?? ''}${order.indirizzo ?? ''} ${order.citta ?? ''}${order.provincia != null ? ' (${order.provincia})' : ''}',
|
|
|
+ style: theme.textTheme.bodySmall?.copyWith(
|
|
|
+ color: cs.onSurfaceVariant,
|
|
|
+ ),
|
|
|
+ maxLines: 2,
|
|
|
+ overflow: TextOverflow.ellipsis,
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 12),
|
|
|
+ Center(
|
|
|
+ child: Text(
|
|
|
+ currencyFormat.format(order.totale ?? 0),
|
|
|
+ style: theme.textTheme.titleLarge?.copyWith(
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 8),
|
|
|
+ Align(
|
|
|
+ alignment: Alignment.centerRight,
|
|
|
+ child: OrderActions(
|
|
|
+ order: order,
|
|
|
+ onEdit: onEdit,
|
|
|
+ onDelete: onDelete,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class _OrderTable extends StatelessWidget {
|
|
|
+ final List<VOrdiniData> orders;
|
|
|
+ final void Function(VOrdiniData) onEdit;
|
|
|
+ final Future<void> Function(VOrdiniData) onDelete;
|
|
|
+ const _OrderTable({
|
|
|
+ required this.orders,
|
|
|
+ required this.onEdit,
|
|
|
+ required this.onDelete,
|
|
|
+ });
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ final theme = Theme.of(context);
|
|
|
+ final cs = theme.colorScheme;
|
|
|
+ final dateFormat = DateFormat('dd/MM/yyyy');
|
|
|
+ final currencyFormat = NumberFormat.currency(
|
|
|
+ symbol: '€ ',
|
|
|
+ decimalDigits: 2,
|
|
|
+ );
|
|
|
+
|
|
|
+ return LayoutBuilder(
|
|
|
+ builder: (context, constraints) {
|
|
|
+ final isCompact = constraints.maxWidth < 1024;
|
|
|
+ return SingleChildScrollView(
|
|
|
+ scrollDirection: Axis.horizontal,
|
|
|
+ child: ConstrainedBox(
|
|
|
+ constraints: BoxConstraints(
|
|
|
+ minWidth: constraints.maxWidth,
|
|
|
+ maxWidth: constraints.maxWidth,
|
|
|
+ ),
|
|
|
+ child: Column(
|
|
|
+ children: [
|
|
|
+ Container(
|
|
|
+ padding: const EdgeInsets.symmetric(
|
|
|
+ horizontal: 24,
|
|
|
+ vertical: 14,
|
|
|
+ ),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: cs.surfaceContainerHighest.withAlpha(50),
|
|
|
+ border: Border(
|
|
|
+ bottom: BorderSide(color: cs.outlineVariant),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ child: Row(
|
|
|
+ children: isCompact
|
|
|
+ ? [
|
|
|
+ _headerCell(context, 'Ordine', flex: 3),
|
|
|
+ _headerCell(context, 'Destinazione', flex: 5),
|
|
|
+ _headerCell(context, 'Totale', flex: 2),
|
|
|
+ _headerCell(context, 'Stato', flex: 2),
|
|
|
+ _headerCell(context, 'Azioni', flex: 2),
|
|
|
+ ]
|
|
|
+ : [
|
|
|
+ _headerCell(context, 'N. Ordine', flex: 1),
|
|
|
+ _headerCell(context, 'Data', flex: 2),
|
|
|
+ _headerCell(context, 'Destinazione', flex: 5),
|
|
|
+ _headerCell(context, 'Totale', flex: 2),
|
|
|
+ _headerCell(context, 'Stato', flex: 1),
|
|
|
+ _headerCell(context, 'Azioni', flex: 2),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ Expanded(
|
|
|
+ child: SingleChildScrollView(
|
|
|
+ scrollDirection: Axis.vertical,
|
|
|
+ child: Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
|
+ children: orders.map((o) {
|
|
|
+ return Container(
|
|
|
+ padding: const EdgeInsets.symmetric(
|
|
|
+ horizontal: 24,
|
|
|
+ vertical: 16,
|
|
|
+ ),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ border: Border(
|
|
|
+ bottom: BorderSide(
|
|
|
+ color: cs.outlineVariant.withAlpha(80),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ child: InkWell(
|
|
|
+ onTap: () => onEdit(o),
|
|
|
+ child: Row(
|
|
|
+ children: isCompact
|
|
|
+ ? [
|
|
|
+ _cell(
|
|
|
+ Column(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ Text(
|
|
|
+ '${o.numeroOrdine ?? ''}',
|
|
|
+ style: theme.textTheme.bodyMedium?.copyWith(
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ fontFamily: 'monospace',
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 4),
|
|
|
+ Text(
|
|
|
+ o.dataOrdine != null
|
|
|
+ ? dateFormat.format(
|
|
|
+ DateTime.parse(
|
|
|
+ o.dataOrdine!,
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ : '',
|
|
|
+ style: theme.textTheme.bodySmall?.copyWith(
|
|
|
+ color: cs.onSurfaceVariant,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ flex: 3,
|
|
|
+ ),
|
|
|
+ _cell(
|
|
|
+ Text(
|
|
|
+ '${o.filiale ?? ''} ${o.indirizzo ?? ''} ${o.citta ?? ''}${o.provincia != null ? ' (${o.provincia})' : ''}',
|
|
|
+ style: theme.textTheme.bodyMedium?.copyWith(
|
|
|
+ color: cs.onSurfaceVariant,
|
|
|
+ ),
|
|
|
+ maxLines: 2,
|
|
|
+ overflow: TextOverflow.ellipsis,
|
|
|
+ ),
|
|
|
+ flex: 5,
|
|
|
+ ),
|
|
|
+ _cell(
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.only(
|
|
|
+ right: 16,
|
|
|
+ ),
|
|
|
+ child: Text(
|
|
|
+ currencyFormat.format(
|
|
|
+ o.totale ?? 0,
|
|
|
+ ),
|
|
|
+ style: theme.textTheme.bodyMedium?.copyWith(
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ ),
|
|
|
+ textAlign: TextAlign.right,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ flex: 2,
|
|
|
+ ),
|
|
|
+ _cell(
|
|
|
+ _StatoPill(stato: o.stato),
|
|
|
+ flex: 2,
|
|
|
+ ),
|
|
|
+ _cell(
|
|
|
+ OrderActions(
|
|
|
+ order: o,
|
|
|
+ onEdit: () => onEdit(o),
|
|
|
+ onDelete: () => onDelete(o),
|
|
|
+ ),
|
|
|
+ flex: 2,
|
|
|
+ ),
|
|
|
+ ]
|
|
|
+ : [
|
|
|
+ _cell(
|
|
|
+ Text(
|
|
|
+ '${o.numeroOrdine ?? ''}',
|
|
|
+ style: theme.textTheme.bodyMedium?.copyWith(
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ fontFamily: 'monospace',
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ flex: 1,
|
|
|
+ ),
|
|
|
+ _cell(
|
|
|
+ Text(
|
|
|
+ o.dataOrdine != null
|
|
|
+ ? dateFormat.format(
|
|
|
+ DateTime.parse(
|
|
|
+ o.dataOrdine!,
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ : '',
|
|
|
+ style: theme.textTheme.bodyMedium?.copyWith(
|
|
|
+ color: cs.onSurfaceVariant,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ flex: 2,
|
|
|
+ ),
|
|
|
+ _cell(
|
|
|
+ Text(
|
|
|
+ '${o.filiale ?? ''} ${o.indirizzo ?? ''} ${o.citta ?? ''}${o.provincia != null ? ' (${o.provincia})' : ''}',
|
|
|
+ style: theme.textTheme.bodyMedium?.copyWith(
|
|
|
+ color: cs.onSurfaceVariant,
|
|
|
+ ),
|
|
|
+ maxLines: 2,
|
|
|
+ overflow: TextOverflow.ellipsis,
|
|
|
+ ),
|
|
|
+ flex: 5,
|
|
|
+ ),
|
|
|
+ _cell(
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.only(
|
|
|
+ right: 16,
|
|
|
+ ),
|
|
|
+ child: Text(
|
|
|
+ currencyFormat.format(
|
|
|
+ o.totale ?? 0,
|
|
|
+ ),
|
|
|
+ style: theme.textTheme.bodyMedium?.copyWith(
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ ),
|
|
|
+ textAlign: TextAlign.right,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ flex: 2,
|
|
|
+ ),
|
|
|
+ _cell(
|
|
|
+ _StatoPill(stato: o.stato),
|
|
|
+ flex: 1,
|
|
|
+ ),
|
|
|
+ _cell(
|
|
|
+ OrderActions(
|
|
|
+ order: o,
|
|
|
+ onEdit: () => onEdit(o),
|
|
|
+ onDelete: () => onDelete(o),
|
|
|
+ ),
|
|
|
+ flex: 2,
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }).toList(),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _headerCell(BuildContext context, String label, {int flex = 1}) {
|
|
|
+ return Expanded(
|
|
|
+ flex: flex,
|
|
|
+ child: Text(
|
|
|
+ label,
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 12,
|
|
|
+ fontWeight: FontWeight.w600,
|
|
|
+ color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
|
+ ),
|
|
|
),
|
|
|
);
|
|
|
}
|
|
|
+
|
|
|
+ Widget _cell(Widget child, {int flex = 1}) {
|
|
|
+ return Expanded(flex: flex, child: child);
|
|
|
+ }
|
|
|
}
|