Bläddra i källkod

WIP: redesign dashboard

Fabio Dalle Ave 5 dagar sedan
förälder
incheckning
1ebf1f4078

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
.flutter-plugins-dependencies


+ 3 - 0
android/app/src/main/AndroidManifest.xml

@@ -1,6 +1,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
    
    <uses-permission android:name="android.permission.INTERNET" />
+   <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+       android:maxSdkVersion="28" />
    
     <application
         android:label="ristoris"

BIN
assets/db/Ristoris.db


+ 1 - 0
lib/core/constants.dart

@@ -14,4 +14,5 @@ class AppConstants {
   static const String kLastUpdateParam = 'LAST_UPDATE';
   static const String kCustomerName = 'Ristoris';
   static const String kBlocco = '001';
+  static const String kAndroidDbDir = '/storage/emulated/0/PowerAge';
 }

+ 4 - 0
lib/core/responsive.dart

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
 class ResponsiveBreakpoints {
   static const double phone = 700;
   static const double tablet = 900;
+  static const double sidebarWidth = 288;
 
   static bool isPhone(BuildContext context) =>
       MediaQuery.of(context).size.width < phone;
@@ -14,6 +15,9 @@ class ResponsiveBreakpoints {
   static bool isDesktop(BuildContext context) =>
       MediaQuery.of(context).size.width >= tablet;
 
+  static bool showPersistentSidebar(BuildContext context) =>
+      MediaQuery.of(context).size.width >= phone;
+
   static EdgeInsets padding(BuildContext context) {
     final w = MediaQuery.of(context).size.width;
     if (w >= tablet) return const EdgeInsets.all(24);

+ 38 - 1
lib/database/database.dart

@@ -81,8 +81,22 @@ class AppDatabase extends _$AppDatabase {
     return isDatabaseEmpty(_dbDirPath ?? '');
   }
 
+  Future<void> _renameColumn(
+    String table,
+    String oldName,
+    String newName,
+  ) async {
+    try {
+      await customStatement(
+        'ALTER TABLE $table RENAME COLUMN $oldName TO $newName',
+      );
+    } catch (_) {
+      // Column might already be renamed or not exist
+    }
+  }
+
   @override
-  int get schemaVersion => 11;
+  int get schemaVersion => 12;
 
   @override
   MigrationStrategy get migration => MigrationStrategy(
@@ -179,6 +193,29 @@ class AppDatabase extends _$AppDatabase {
           LEFT JOIN Clienti c ON c.idCliente = s.idCliente
         ''');
           }
+          if (from < 12) {
+            await _renameColumn('Agenti', 'CodAgente', 'codAgente');
+            await _renameColumn('Agenti', 'Pwd', 'pwd');
+            await _renameColumn('Promozioni', 'TipoPromozione', 'tipoPromozione');
+            await _renameColumn('Promozioni', 'Descrizione', 'descrizione');
+            await _renameColumn('Promozioni', 'DataInizio', 'dataInizio');
+            await _renameColumn('Promozioni', 'DataFine', 'dataFine');
+            await _renameColumn('Promozioni', 'LimiteQta', 'limiteQta');
+            await _renameColumn('Promozioni', 'QuantitaOmaggio', 'quantitaOmaggio');
+            await _renameColumn('Promozioni', 'LimiteImporto', 'limiteImporto');
+            await _renameColumn('Promozioni', 'Stato', 'stato');
+            await _renameColumn('Promozioni', 'Priorita', 'priorita');
+            await _renameColumn('PromoArticoli', 'QuantitaOmaggio', 'quantitaOmaggio');
+            await _renameColumn('PromoOmaggi', 'Riga', 'riga');
+            await _renameColumn('PromoOmaggi', 'Quantita', 'quantita');
+            await _renameColumn('Listini_cliente', 'ValidoDa', 'validoDa');
+            await _renameColumn('Listini_cliente', 'ValidoA', 'validoA');
+            await _renameColumn('Listini_cliente', 'Prezzo', 'prezzo');
+            await _renameColumn('Listini_cliente', 'Annullato', 'annullato');
+            await _renameColumn('Clienti_Agente', 'Attivo', 'attivo');
+            await _renameColumn('Clienti', 'partitaIVA', 'partitaIva');
+            await _renameColumn('Clienti', 'Pod', 'pod');
+          }
         },
       );
 }

+ 25 - 5
lib/database/db_connector_io.dart

@@ -1,4 +1,5 @@
 import 'dart:io';
+import 'dart:developer' as dev;
 import 'package:drift/native.dart';
 import 'package:drift/drift.dart';
 import 'package:path/path.dart' as p;
@@ -14,18 +15,37 @@ QueryExecutor createDb(String dirPath, String dbName) {
 }
 
 Future<String> ensureDbReady() async {
-  final dir = await getApplicationDocumentsDirectory();
-  final dbFile = File(p.join(dir.path, AppConstants.kDbName));
+  String dirPath;
+
+  if (Platform.isAndroid) {
+    final extDir = Directory(AppConstants.kAndroidDbDir);
+    if (!await extDir.exists()) {
+      await extDir.create(recursive: true);
+    }
+    dirPath = extDir.path;
+  } else {
+    final dir = await getApplicationDocumentsDirectory();
+    dirPath = dir.path;
+  }
+
+  final dbFile = File(p.join(dirPath, AppConstants.kDbName));
   if (!await dbFile.exists()) {
     try {
       final data = await rootBundle.load(AppConstants.kAssetsDbPath);
       await dbFile.create(recursive: true);
       await dbFile.writeAsBytes(data.buffer.asUint8List());
-    } catch (_) {
-      // No seed DB in assets, will create empty
+    } catch (e) {
+      dev.log('Errore copia seed DB: $e');
     }
   }
-  return dir.path;
+
+  if (!await dbFile.exists()) {
+    throw Exception(
+      'Impossibile creare il file database in $dirPath',
+    );
+  }
+
+  return dirPath;
 }
 
 Future<bool> isDatabaseEmpty(String dirPath) async {

+ 0 - 1
lib/database/tables/listini.dart

@@ -10,7 +10,6 @@ class Listini extends Table {
   IntColumn get attivo =>
       integer().named('attivo').withDefault(const Constant(1))();
   TextColumn get dataModifica => text().named('dataModifica').nullable()();
-  IntColumn get listinoAgente => integer().named('listinoAgente').nullable()();
 
   @override
   String get tableName => 'Listini';

+ 1 - 1
lib/database/tables/scadenze.dart

@@ -2,7 +2,7 @@ import 'package:drift/drift.dart';
 
 @DataClassName('Scadenza')
 class Scadenze extends Table {
-  IntColumn get idScadenza => integer().named('idScadenza').autoIncrement()();
+  IntColumn get idScadenza => integer().autoIncrement()();
   IntColumn get idCliente => integer().named('idCliente').nullable()();
   IntColumn get idAgente => integer().named('idAgente').nullable()();
   TextColumn get chiave => text().named('chiave').nullable()();

+ 1 - 1
lib/database/tables/statistiche_vendita.dart

@@ -2,7 +2,7 @@ import 'package:drift/drift.dart';
 
 @DataClassName('StatisticheVenditaData')
 class StatisticheVendita extends Table {
-  IntColumn get id => integer()();
+  IntColumn get id => integer().autoIncrement()();
   IntColumn get idAgente => integer().named('idAgente').nullable()();
   IntColumn get idCliente => integer().named('idCliente').nullable()();
   IntColumn get idArticolo => integer().named('idArticolo').nullable()();

+ 68 - 4
lib/main.dart

@@ -1,17 +1,81 @@
+import 'dart:io';
+
 import 'package:flutter/material.dart';
 import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:permission_handler/permission_handler.dart';
 import 'database/database.dart';
 import 'app.dart';
 
 void main() async {
   WidgetsFlutterBinding.ensureInitialized();
 
-  // Ensure database is ready (copy seed if needed)
+  if (Platform.isAndroid) {
+    var status = await Permission.manageExternalStorage.status;
+    if (!status.isGranted) {
+      await Permission.manageExternalStorage.request();
+
+      for (var i = 0; i < 30; i++) {
+        await Future.delayed(const Duration(seconds: 1));
+        status = await Permission.manageExternalStorage.status;
+        if (status.isGranted) break;
+      }
+
+      if (!status.isGranted) {
+        runApp(
+          const ProviderScope(child: _PermissionDeniedApp()),
+        );
+        return;
+      }
+    }
+  }
+
   await AppDatabase.ensureDatabaseReady();
 
   runApp(
-    const ProviderScope(
-      child: RistorisApp(),
-    ),
+    const ProviderScope(child: RistorisApp()),
   );
 }
+
+class _PermissionDeniedApp extends StatelessWidget {
+  const _PermissionDeniedApp();
+
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      home: Scaffold(
+        body: Center(
+          child: Padding(
+            padding: const EdgeInsets.all(32),
+            child: Column(
+              mainAxisSize: MainAxisSize.min,
+              children: [
+                const Icon(
+                  Icons.folder_off,
+                  size: 64,
+                  color: Colors.red,
+                ),
+                const SizedBox(height: 24),
+                const Text(
+                  'Permesso di accesso ai file non concesso',
+                  textAlign: TextAlign.center,
+                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+                ),
+                const SizedBox(height: 12),
+                const Text(
+                  'L\'app ha bisogno di accedere alla memoria del dispositivo per salvare il database.',
+                  textAlign: TextAlign.center,
+                ),
+                const SizedBox(height: 24),
+                FilledButton.icon(
+                  onPressed: () => openAppSettings(),
+                  icon: const Icon(Icons.settings),
+                  label: const Text('Apri Impostazioni'),
+                ),
+              ],
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}

+ 45 - 30
lib/ui/pages/dashboard/dashboard_page.dart

@@ -6,7 +6,8 @@ import 'package:ristoris/providers/database_provider.dart';
 import '../../../providers/auth_provider.dart';
 import '../../../providers/dashboard_provider.dart';
 import '../../../core/responsive.dart';
-import '../../widgets/app_drawer.dart';
+import '../../../services/database_export_service.dart';
+import '../../widgets/persistent_sidebar.dart';
 
 class DashboardPage extends ConsumerWidget {
   const DashboardPage({super.key});
@@ -20,38 +21,52 @@ class DashboardPage extends ConsumerWidget {
     final agentiAsync = ref.watch(agentiListProvider);
     final theme = Theme.of(context);
 
+    final dashboardBody = SafeArea(
+      child: dashboardAsync?.when(
+            data: (data) {
+              final agentiList = agentiAsync.asData?.value;
+              final hasMultipleAgents =
+                  agentiList != null && agentiList.length > 1;
+              return _DashboardContent(
+                data: data,
+                theme: theme,
+                hasMultipleAgents: hasMultipleAgents,
+              );
+            },
+            loading: () => const Center(child: CircularProgressIndicator()),
+            error: (e, _) => Center(child: Text('Errore: $e')),
+          ) ??
+          const Center(child: CircularProgressIndicator()),
+    );
+
+    if (ResponsiveBreakpoints.showPersistentSidebar(context)) {
+      return Row(
+        children: [
+          PersistentSidebar(
+            onExportDatabase: () => DatabaseExportService.export(context),
+          ),
+          Expanded(
+            child: Scaffold(
+              appBar: AppBar(
+                title: Text(
+                  'Benvenuto, ${agente?.ragioneSociale ?? ''}',
+                ),
+              ),
+              body: dashboardBody,
+            ),
+          ),
+        ],
+      );
+    }
+
     return Scaffold(
       appBar: AppBar(title: Text('Benvenuto, ${agente?.ragioneSociale ?? ''}')),
-      drawer: AppDrawer(
-        agente: agente,
-        onDashboard: () => context.go('/dashboard'),
-        onOrders: () => context.go('/dashboard/orders'),
-        onCustomers: () => context.go('/dashboard/customers'),
-        onCatalog: () => context.go('/dashboard/catalog'),
-        onPromotions: () => context.go('/dashboard/promotions'),
-        onPayments: () => context.go('/dashboard/payments'),
-        onStatistics: () => context.go('/dashboard/statistics'),
-        onSync: () => context.go('/dashboard/sync'),
-        onSwitchAgent: () => context.go('/dashboard/switch-agent'),
-        onLogout: () => ref.read(authProvider.notifier).logout(),
-      ),
-      body: SafeArea(
-        child: dashboardAsync?.when(
-              data: (data) {
-                final agentiList = agentiAsync.asData?.value;
-                final hasMultipleAgents =
-                    agentiList != null && agentiList.length > 1;
-                return _DashboardContent(
-                  data: data,
-                  theme: theme,
-                  hasMultipleAgents: hasMultipleAgents,
-                );
-              },
-              loading: () => const Center(child: CircularProgressIndicator()),
-              error: (e, _) => Center(child: Text('Errore: $e')),
-            ) ??
-            const Center(child: CircularProgressIndicator()),
+      drawer: Drawer(
+        child: PersistentSidebar(
+          onExportDatabase: () => DatabaseExportService.export(context),
+        ),
       ),
+      body: dashboardBody,
     );
   }
 }

+ 4 - 0
linux/flutter/generated_plugin_registrant.cc

@@ -7,9 +7,13 @@
 #include "generated_plugin_registrant.h"
 
 #include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
+#include <url_launcher_linux/url_launcher_plugin.h>
 
 void fl_register_plugins(FlPluginRegistry* registry) {
   g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
       fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
   sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
+  g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
+      fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
+  url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
 }

+ 1 - 0
linux/flutter/generated_plugins.cmake

@@ -4,6 +4,7 @@
 
 list(APPEND FLUTTER_PLUGIN_LIST
   sqlite3_flutter_libs
+  url_launcher_linux
 )
 
 list(APPEND FLUTTER_FFI_PLUGIN_LIST

+ 4 - 0
macos/Flutter/GeneratedPluginRegistrant.swift

@@ -6,13 +6,17 @@ import FlutterMacOS
 import Foundation
 
 import connectivity_plus
+import file_picker
 import path_provider_foundation
+import share_plus
 import sqflite_darwin
 import sqlite3_flutter_libs
 
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
   ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
+  FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
   PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
+  SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
   SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
   Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
 }

+ 7 - 0
pubspec.yaml

@@ -39,6 +39,13 @@ dependencies:
   connectivity_plus: ^6.0.0
   google_fonts: ^6.1.0
 
+  # Export / Share
+  share_plus: ^10.0.0
+  file_picker: ^8.0.0
+
+  # Permissions
+  permission_handler: ^11.0.0
+
 dev_dependencies:
   flutter_test:
     sdk: flutter

+ 9 - 0
windows/flutter/generated_plugin_registrant.cc

@@ -7,11 +7,20 @@
 #include "generated_plugin_registrant.h"
 
 #include <connectivity_plus/connectivity_plus_windows_plugin.h>
+#include <permission_handler_windows/permission_handler_windows_plugin.h>
+#include <share_plus/share_plus_windows_plugin_c_api.h>
 #include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
+#include <url_launcher_windows/url_launcher_windows.h>
 
 void RegisterPlugins(flutter::PluginRegistry* registry) {
   ConnectivityPlusWindowsPluginRegisterWithRegistrar(
       registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
+  PermissionHandlerWindowsPluginRegisterWithRegistrar(
+      registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
+  SharePlusWindowsPluginCApiRegisterWithRegistrar(
+      registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
   Sqlite3FlutterLibsPluginRegisterWithRegistrar(
       registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin"));
+  UrlLauncherWindowsRegisterWithRegistrar(
+      registry->GetRegistrarForPlugin("UrlLauncherWindows"));
 }

+ 3 - 0
windows/flutter/generated_plugins.cmake

@@ -4,7 +4,10 @@
 
 list(APPEND FLUTTER_PLUGIN_LIST
   connectivity_plus
+  permission_handler_windows
+  share_plus
   sqlite3_flutter_libs
+  url_launcher_windows
 )
 
 list(APPEND FLUTTER_FFI_PLUGIN_LIST

Vissa filer visades inte eftersom för många filer har ändrats