diff --git a/claude.md b/claude.md index 91a01f3..1fcd064 100644 --- a/claude.md +++ b/claude.md @@ -1,7 +1,7 @@ # Flutter Retail POS App Expert Guidelines ## 🎯 App Overview -A Flutter-based Point of Sale (POS) retail application for managing products, categories, and sales transactions with an intuitive tab-based interface. +A Flutter-based Point of Sale (POS) retail application for managing products, categories, inventory, sales transactions, and business analytics with an intuitive tab-based interface and comprehensive inventory management system. --- @@ -15,27 +15,27 @@ A Flutter-based Point of Sale (POS) retail application for managing products, ca You have access to these expert subagents - USE THEM PROACTIVELY: #### 🎨 **flutter-widget-expert** -- **MUST BE USED for**: Product cards, category grids, cart UI, tab navigation, custom widgets -- **Triggers**: "create widget", "build UI", "product card", "category grid", "layout", "animation" +- **MUST BE USED for**: Product cards, category grids, cart UI, tab navigation, custom widgets, dashboard charts +- **Triggers**: "create widget", "build UI", "product card", "category grid", "layout", "animation", "chart", "dashboard" #### πŸ“Š **riverpod-expert** -- **MUST BE USED for**: Cart state, product selection, category filtering, sales state management -- **Triggers**: "state management", "provider", "cart", "async state", "data flow", "sales state" +- **MUST BE USED for**: Cart state, product selection, category filtering, inventory state, sales state management +- **Triggers**: "state management", "provider", "cart", "async state", "data flow", "sales state", "inventory state" #### πŸ—„οΈ **hive-expert** -- **MUST BE USED for**: Product storage, category database, sales history, local cache -- **Triggers**: "database", "cache", "hive", "products", "categories", "persistence", "offline" +- **MUST BE USED for**: Product storage, category database, sales history, inventory tracking, local cache +- **Triggers**: "database", "cache", "hive", "products", "categories", "persistence", "offline", "inventory" #### 🌐 **api-integration-expert** -- **MUST BE USED for**: Product sync, inventory API, payment processing, backend integration -- **Triggers**: "API", "HTTP", "sync", "dio", "REST", "backend", "payment" +- **MUST BE USED for**: Product sync, inventory API, order processing, backend integration, CSV import/export +- **Triggers**: "API", "HTTP", "sync", "dio", "REST", "backend", "import", "export" #### πŸ—οΈ **architecture-expert** - **MUST BE USED for**: Feature organization, dependency injection, clean architecture setup - **Triggers**: "architecture", "structure", "organization", "clean code", "refactor" #### ⚑ **performance-expert** -- **MUST BE USED for**: Product image caching, grid scrolling, memory optimization +- **MUST BE USED for**: Product image caching, grid scrolling, memory optimization, dashboard performance - **Triggers**: "performance", "optimization", "memory", "image cache", "slow", "lag", "scroll" ### 🎯 DELEGATION STRATEGY @@ -55,14 +55,14 @@ You have access to these expert subagents - USE THEM PROACTIVELY: > Have the riverpod-expert design the shopping cart state management > Ask the hive-expert to create the product and category database schema > Use the api-integration-expert to implement product sync with backend -> Have the architecture-expert organize the sales feature structure +> Have the architecture-expert organize the inventory feature structure > Ask the performance-expert to optimize the product grid scrolling ``` --- ## Flutter Best Practices -- Use Flutter 3.35.x features and Material 3 design +- Use Flutter 3.x features and Material 3 design - Implement clean architecture with Riverpod for state management - Use Hive CE for local database and offline-first functionality - Follow proper dependency injection with GetIt @@ -95,6 +95,7 @@ lib/ formatters.dart # Price, date formatters validators.dart # Input validation extensions.dart # Dart extensions + csv_helper.dart # CSV import/export widgets/ custom_button.dart # Reusable buttons loading_indicator.dart # Loading states @@ -120,10 +121,12 @@ lib/ remove_from_cart.dart clear_cart.dart calculate_total.dart + apply_discount.dart presentation/ providers/ cart_provider.dart cart_total_provider.dart + discount_provider.dart pages/ home_page.dart widgets/ @@ -131,6 +134,7 @@ lib/ cart_item_card.dart cart_summary.dart checkout_button.dart + discount_dialog.dart products/ data/ @@ -139,11 +143,15 @@ lib/ product_local_datasource.dart models/ product_model.dart + product_variant_model.dart + supplier_model.dart repositories/ product_repository_impl.dart domain/ entities/ product.dart + product_variant.dart + supplier.dart repositories/ product_repository.dart usecases/ @@ -151,18 +159,29 @@ lib/ get_products_by_category.dart search_products.dart sync_products.dart + create_product.dart + update_product.dart + delete_product.dart + manage_variants.dart + import_products_csv.dart + export_products_csv.dart presentation/ providers/ products_provider.dart product_search_provider.dart product_filter_provider.dart + product_form_provider.dart pages/ products_page.dart - widgets/ + product_detail_page.dart + product_form_page.dart + widgets: product_grid.dart product_card.dart product_search_bar.dart product_filter_chip.dart + product_form.dart + variant_list.dart categories/ data/ @@ -171,26 +190,123 @@ lib/ category_local_datasource.dart models/ category_model.dart + tag_model.dart repositories/ category_repository_impl.dart domain/ entities/ category.dart + tag.dart repositories/ category_repository.dart - usecases/ + usecases: get_all_categories.dart get_category_by_id.dart sync_categories.dart + create_category.dart + update_category.dart + delete_category.dart + manage_tags.dart presentation/ providers/ categories_provider.dart selected_category_provider.dart + tags_provider.dart pages/ categories_page.dart + category_form_page.dart widgets/ category_grid.dart category_card.dart + category_form.dart + tag_chip.dart + + inventory/ + data/ + datasources/ + inventory_local_datasource.dart + models/ + inventory_alert_model.dart + repositories/ + inventory_repository_impl.dart + domain/ + entities/ + inventory_alert.dart + repositories/ + inventory_repository.dart + usecases: + get_low_stock_products.dart + update_stock_levels.dart + batch_update_stock.dart + presentation/ + providers: + inventory_provider.dart + stock_alerts_provider.dart + pages: + low_stock_alerts_page.dart + widgets: + stock_level_indicator.dart + alert_badge.dart + batch_update_form.dart + + orders/ + data/ + datasources/ + order_local_datasource.dart + models/ + order_model.dart + order_item_model.dart + repositories/ + order_repository_impl.dart + domain/ + entities: + order.dart + order_item.dart + repositories: + order_repository.dart + usecases: + create_order.dart + get_order_history.dart + presentation: + providers: + orders_provider.dart + pages: + order_history_page.dart + widgets: + order_card.dart + + dashboard/ + data: + datasources: + analytics_local_datasource.dart + models: + sales_stats_model.dart + product_performance_model.dart + repositories: + analytics_repository_impl.dart + domain: + entities: + sales_stats.dart + product_performance.dart + repositories: + analytics_repository.dart + usecases: + get_daily_revenue.dart + get_monthly_revenue.dart + get_best_selling_products.dart + get_category_performance.dart + presentation: + providers: + dashboard_provider.dart + revenue_provider.dart + best_sellers_provider.dart + pages: + dashboard_page.dart + widgets: + revenue_chart.dart + sales_summary_card.dart + best_sellers_list.dart + category_performance_chart.dart settings/ data/ @@ -205,7 +321,7 @@ lib/ app_settings.dart repositories/ settings_repository.dart - usecases/ + usecases: get_settings.dart update_settings.dart presentation/ @@ -224,6 +340,7 @@ lib/ app_bottom_nav.dart # Tab navigation custom_app_bar.dart # Reusable app bar price_display.dart # Currency formatting + stock_badge.dart # Stock level indicator main.dart app.dart # Root widget with ProviderScope @@ -233,7 +350,9 @@ test/ features/ products/ categories/ - home/ + inventory/ + orders/ + dashboard/ widget/ integration/ ``` @@ -243,7 +362,7 @@ test/ # App Context - Retail POS Application ## About This App -A comprehensive Flutter-based Point of Sale (POS) application designed for retail businesses. The app enables merchants to manage their product inventory, organize items by categories, process sales transactions, and maintain business settingsβ€”all through an intuitive tab-based interface optimized for speed and efficiency. +A comprehensive Flutter-based Point of Sale (POS) application designed for retail businesses. The app enables merchants to manage their complete product inventory with variants, track stock levels, process sales transactions, analyze business performance, and maintain business settingsβ€”all through an intuitive tab-based interface optimized for speed and efficiency. ## Target Users - **Retail Store Owners**: Small to medium-sized retail businesses @@ -253,73 +372,133 @@ A comprehensive Flutter-based Point of Sale (POS) application designed for retai ## Core Features -### πŸ“± Tab-Based Navigation (4 Tabs) +### πŸ“± Tab-Based Navigation (4 Main Tabs + Extended Features) #### Tab 1: Home/POS Screen **Purpose**: Primary sales interface for selecting products and processing transactions **Key Components**: -- **Product Selector**: Quick access to frequently sold products +- **Product Selector**: Quick access to frequently sold products with variants - **Shopping Cart**: Real-time cart display with items, quantities, prices - **Cart Management**: Add, remove, update quantities - **Cart Summary**: Subtotal, tax, discounts, total calculation - **Checkout Flow**: Complete transaction and process payment -- **Quick Actions**: Clear cart, apply discounts, process returns +- **Quick Actions**: Clear cart, apply discounts, process returns, create order +- **Discount System**: Apply percentage or fixed amount discounts to cart or items **State Management**: - Cart state (items, quantities, totals) -- Selected product state -- Discount state +- Selected product and variant state +- Discount state (cart-level and item-level) - Transaction state +- Order creation state **Data Requirements**: - Real-time cart updates -- Price calculations +- Price calculations with variants - Tax computation - Transaction logging +- Order generation + +**Business Logic**: +- Calculate subtotals including variants +- Apply discounts (percentage, fixed, promotional) +- Compute tax based on settings +- Generate order from cart +- Validate stock before checkout +- Process payment and create transaction record #### Tab 2: Products Grid -**Purpose**: Browse and search all available products +**Purpose**: Browse, search, and manage all available products **Key Components**: - **Product Grid**: Responsive grid layout with product cards -- **Product Cards**: Image, name, price, stock status, category -- **Search Bar**: Real-time product search -- **Filter Options**: Category filter, price range, availability -- **Sort Options**: Name, price, category, popularity +- **Product Cards**: Image, name, price, stock status, category, variants +- **Search Bar**: Real-time product search across name, description, SKU +- **Filter Options**: Category filter, price range, availability, supplier, tags +- **Sort Options**: Name, price, category, popularity, stock level - **Empty States**: No products found UI +- **Quick Actions**: Add product, edit product, manage stock +- **Batch Operations**: Select multiple products for bulk updates + +**Product Management**: +- **Add/Edit Products**: + - Basic info (name, description, SKU, barcode) + - Pricing (cost, retail price, sale price) + - Stock levels + - Product images (multiple) + - Category assignment + - Tags for organization + - Supplier information +- **Product Variants**: + - Size variants (S, M, L, XL) + - Color variants + - Custom variant types + - Individual SKU per variant + - Separate pricing per variant + - Separate stock per variant +- **Supplier Management**: + - Add/edit suppliers + - Link products to suppliers + - Track supplier contact info + - View products by supplier **State Management**: - Products list state (all products) - Search query state -- Filter state (category, price range) +- Filter state (category, price, supplier, tags) - Sort state +- Product form state +- Variant management state +- Supplier state **Data Requirements**: - Product list from Hive (offline-first) -- Product images (cached) +- Product images (cached with variants) - Product search indexing - Category relationships +- Supplier relationships +- Tag associations +- Variant data with individual stock #### Tab 3: Categories Grid -**Purpose**: View and manage product categories +**Purpose**: View and manage product categories and tags **Key Components**: - **Category Grid**: Visual grid of all categories - **Category Cards**: Icon/image, name, product count - **Category Selection**: Navigate to products in category - **Empty States**: No categories UI +- **Category Management**: Add, edit, delete categories +- **Tag Management**: Create and manage product tags +- **Quick Actions**: Create category, manage tags + +**Category Management**: +- Create new categories +- Edit category details (name, icon, description) +- Delete categories (with product reassignment) +- Set category colors +- Organize products by category + +**Tag Management**: +- Create product tags for organization +- Apply multiple tags to products +- Filter products by tags +- Color-coded tags **State Management**: - Categories list state - Selected category state - Products by category state +- Tags state +- Category form state **Data Requirements**: - Category list from Hive - Product count per category - Category images (cached) - Category-product relationships +- Tag associations #### Tab 4: Settings **Purpose**: App configuration and business settings @@ -330,7 +509,7 @@ A comprehensive Flutter-based Point of Sale (POS) application designed for retai - **Currency Settings**: Currency format and symbol - **Tax Configuration**: Tax rates and rules - **Business Info**: Store name, contact, address -- **Data Management**: Sync, backup, clear cache +- **Data Management**: Sync, backup, clear cache, CSV import/export - **About**: App version, credits, legal **State Management**: @@ -338,11 +517,204 @@ A comprehensive Flutter-based Point of Sale (POS) application designed for retai - Theme mode state - Language state - Sync state +- Import/export state **Data Requirements**: - App settings from Hive - User preferences - Business configuration +- CSV templates + +--- + +### πŸ“¦ Inventory Management (Extended Feature) + +**Purpose**: Basic inventory monitoring and alerts + +**Key Features**: + +#### Low Stock Alerts +- **Alert Configuration**: + - Set minimum stock level per product + - Enable/disable alerts + - Alert threshold (e.g., alert when < 10 units) +- **Alert Notifications**: + - Dashboard badge showing low-stock count + - Low stock products list + - Visual indicators on product cards + - Sort products by stock level + +#### CSV Import/Export +- **Import Products**: + - CSV template download + - Bulk product import + - Validation and error reporting + - Preview before import + - Update existing or create new +- **Export Products**: + - Export all products to CSV + - Export filtered products + - Include variants in export + - Custom field selection + +#### Batch Updates +- **Multi-Select Mode**: Select multiple products +- **Bulk Operations**: + - Update prices (increase/decrease by %) + - Update stock levels + - Change categories + - Apply tags + - Update supplier + - Delete multiple products +- **Preview Changes**: Review before applying + +**Data Models**: +```dart +InventoryAlert: + - productId, alertThreshold, isActive + - currentStock, minimumStock +``` + +**State Management**: +- Inventory state (stock levels) +- Low stock alerts state +- Import/export progress state +- Batch operation state + +--- + +### πŸ’° Orders & Sales Management (Extended Feature) + +**Purpose**: Simple order tracking and basic discounts + +**Key Features**: + +#### Order Management +- **Create Orders**: Convert cart to order +- **Order Details**: + - Order number (auto-generated) + - Order items with quantities + - Subtotal, discount, total + - Payment method + - Order status (completed, cancelled) + - Order date/time +- **Order History**: View all past orders +- **Order Actions**: View details, basic order info + +#### Basic Discount System +- **Discount Types**: + - Percentage discount (e.g., 10% off) + - Fixed amount discount (e.g., $5 off) + - Cart-level discounts only +- **Apply Discounts**: During checkout to entire cart + +#### Order History +- **Simple List**: Chronological display of orders +- **Order Details**: Tap to view full order information +- **Basic Filter**: Filter by date (today, week, month) + +**Data Models**: +```dart +Order: + - id, orderNumber + - items (List) + - subtotal, discount, total + - paymentMethod, orderStatus + - createdAt + +OrderItem: + - productId, productName, variantInfo + - quantity, unitPrice, lineTotal +``` + +**State Management**: +- Orders list state +- Order creation state +- Basic discount state + +--- + +### πŸ“Š Sales Dashboard (Extended Feature) + +**Purpose**: Business analytics and performance insights + +**Key Features**: + +#### Revenue Analytics +- **Daily Revenue**: + - Today's total sales + - Number of transactions + - Average transaction value + - Revenue trend chart (hourly) +- **Monthly Revenue**: + - Current month total + - Month-over-month comparison + - Revenue trend chart (daily) + - Projected month-end revenue +- **Custom Date Range**: Select any date range for analysis +- **Revenue by Payment Method**: Cash vs Card breakdown + +#### Best-Selling Products +- **Top Products**: + - Ranked by quantity sold + - Ranked by revenue generated + - Timeframe selector (today, week, month, all-time) +- **Product Performance**: + - Units sold + - Revenue generated + - Average price + - Trend indicators (↑↓) +- **Visual Display**: Charts and lists + +#### Category Performance +- **Sales by Category**: + - Revenue per category + - Units sold per category + - Category comparison + - Pie chart visualization +- **Category Trends**: Growth/decline indicators + +#### Dashboard Widgets +- **Summary Cards**: + - Total Revenue (today/week/month) + - Total Transactions + - Average Transaction Value + - Low Stock Alerts Count +- **Charts**: + - Revenue line chart + - Category pie chart + - Best sellers bar chart + - Sales trend graph +- **Quick Stats**: + - Products sold today + - Most popular product + - Total products in inventory + - Categories count + +**Data Requirements**: +- Transaction history from database +- Product sales data +- Category sales data +- Time-series data for charts +- Aggregated statistics + +**State Management**: +- Dashboard stats state +- Revenue data state +- Best sellers state +- Category performance state +- Date range filter state +- Chart data state + +**Analytics Calculations**: +- Sum revenue by period +- Count transactions +- Calculate average transaction value +- Rank products by sales +- Group sales by category +- Trend analysis (increase/decrease %) + +--- ## Technical Architecture @@ -352,75 +724,34 @@ A comprehensive Flutter-based Point of Sale (POS) application designed for retai ```dart // Cart Management -@riverpod -class Cart extends _$Cart { - @override - List build() => []; - - void addItem(Product product, int quantity) { /* ... */ } - void removeItem(String productId) { /* ... */ } - void updateQuantity(String productId, int quantity) { /* ... */ } - void clearCart() { /* ... */ } -} - -@riverpod -class CartTotal extends _$CartTotal { - @override - double build() { - final items = ref.watch(cartProvider); - return items.fold(0.0, (sum, item) => sum + (item.price * item.quantity)); - } -} +final cartProvider = NotifierProvider>(Cart.new); +final cartTotalProvider = Provider((ref) { ... }); +final discountProvider = NotifierProvider(Discount.new); // Products Management -@riverpod -class Products extends _$Products { - @override - Future> build() async { - return await ref.read(productRepositoryProvider).getAllProducts(); - } - - Future syncProducts() async { /* ... */ } -} - -@riverpod -class FilteredProducts extends _$FilteredProducts { - @override - List build() { - final products = ref.watch(productsProvider).value ?? []; - final searchQuery = ref.watch(searchQueryProvider); - final selectedCategory = ref.watch(selectedCategoryProvider); - - return products.where((p) { - final matchesSearch = p.name.toLowerCase().contains(searchQuery.toLowerCase()); - final matchesCategory = selectedCategory == null || p.categoryId == selectedCategory; - return matchesSearch && matchesCategory; - }).toList(); - } -} +final productsProvider = AsyncNotifierProvider>(Products.new); +final filteredProductsProvider = Provider>((ref) { ... }); +final productFormProvider = NotifierProvider(ProductForm.new); +final variantsProvider = NotifierProvider>(Variants.new); // Categories Management -@riverpod -class Categories extends _$Categories { - @override - Future> build() async { - return await ref.read(categoryRepositoryProvider).getAllCategories(); - } - - Future syncCategories() async { /* ... */ } -} +final categoriesProvider = AsyncNotifierProvider>(Categories.new); +final tagsProvider = NotifierProvider>(Tags.new); + +// Inventory Management +final inventoryProvider = NotifierProvider>(Inventory.new); +final stockAlertsProvider = Provider>((ref) { ... }); + +// Orders Management +final ordersProvider = AsyncNotifierProvider>(Orders.new); + +// Dashboard Analytics +final dashboardStatsProvider = Provider((ref) { ... }); +final revenueProvider = Provider((ref) { ... }); +final bestSellersProvider = Provider>((ref) { ... }); // Settings Management -@riverpod -class AppSettings extends _$AppSettings { - @override - Future build() async { - return await ref.read(settingsRepositoryProvider).getSettings(); - } - - Future updateTheme(ThemeMode mode) async { /* ... */ } - Future updateLanguage(String locale) async { /* ... */ } -} +final settingsProvider = AsyncNotifierProvider(AppSettings.new); ``` ### Database Schema (Hive CE) @@ -430,153 +761,124 @@ class AppSettings extends _$AppSettings { ```dart // Box Names const String productsBox = 'products'; +const String variantsBox = 'variants'; const String categoriesBox = 'categories'; +const String tagsBox = 'tags'; +const String suppliersBox = 'suppliers'; const String cartBox = 'cart'; +const String ordersBox = 'orders'; const String settingsBox = 'settings'; -const String transactionsBox = 'transactions'; ``` -#### Product Model +#### Product Model (Enhanced) ```dart @HiveType(typeId: 0) class Product extends HiveObject { - @HiveField(0) - final String id; - - @HiveField(1) - final String name; - - @HiveField(2) - final String description; - - @HiveField(3) - final double price; - - @HiveField(4) - final String? imageUrl; - - @HiveField(5) - final String categoryId; - - @HiveField(6) - final int stockQuantity; - - @HiveField(7) - final bool isAvailable; - - @HiveField(8) - final DateTime createdAt; - - @HiveField(9) - final DateTime updatedAt; + @HiveField(0) final String id; + @HiveField(1) final String name; + @HiveField(2) final String description; + @HiveField(3) final double price; + @HiveField(4) final double? costPrice; + @HiveField(5) final double? salePrice; + @HiveField(6) final String? sku; + @HiveField(7) final String? barcode; + @HiveField(8) final List? imageUrls; + @HiveField(9) final String categoryId; + @HiveField(10) final int stockQuantity; + @HiveField(11) final bool isAvailable; + @HiveField(12) final List tags; + @HiveField(13) final String? supplierId; + @HiveField(14) final bool hasVariants; + @HiveField(15) final List variantIds; + @HiveField(16) final int? lowStockThreshold; + @HiveField(17) final DateTime createdAt; + @HiveField(18) final DateTime updatedAt; +} +``` + +#### Product Variant Model +```dart +@HiveType(typeId: 1) +class ProductVariant extends HiveObject { + @HiveField(0) final String id; + @HiveField(1) final String productId; + @HiveField(2) final String name; // e.g., "Large - Red" + @HiveField(3) final String? sku; + @HiveField(4) final double? priceAdjustment; + @HiveField(5) final int stockQuantity; + @HiveField(6) final Map attributes; // {size: "L", color: "Red"} + @HiveField(7) final String? imageUrl; + @HiveField(8) final DateTime createdAt; } ``` #### Category Model ```dart -@HiveType(typeId: 1) -class Category extends HiveObject { - @HiveField(0) - final String id; - - @HiveField(1) - final String name; - - @HiveField(2) - final String? description; - - @HiveField(3) - final String? iconPath; - - @HiveField(4) - final String? color; // Hex color string - - @HiveField(5) - final int productCount; - - @HiveField(6) - final DateTime createdAt; -} -``` - -#### Cart Item Model -```dart @HiveType(typeId: 2) -class CartItem extends HiveObject { - @HiveField(0) - final String productId; - - @HiveField(1) - final String productName; - - @HiveField(2) - final double price; - - @HiveField(3) - final int quantity; - - @HiveField(4) - final String? imageUrl; - - @HiveField(5) - final DateTime addedAt; +class Category extends HiveObject { + @HiveField(0) final String id; + @HiveField(1) final String name; + @HiveField(2) final String? description; + @HiveField(3) final String? iconPath; + @HiveField(4) final String? color; + @HiveField(5) final int productCount; + @HiveField(6) final DateTime createdAt; } ``` -#### Transaction Model +#### Tag Model ```dart @HiveType(typeId: 3) -class Transaction extends HiveObject { - @HiveField(0) - final String id; - - @HiveField(1) - final List items; - - @HiveField(2) - final double subtotal; - - @HiveField(3) - final double tax; - - @HiveField(4) - final double discount; - - @HiveField(5) - final double total; - - @HiveField(6) - final DateTime completedAt; - - @HiveField(7) - final String paymentMethod; +class Tag extends HiveObject { + @HiveField(0) final String id; + @HiveField(1) final String name; + @HiveField(2) final String? color; + @HiveField(3) final DateTime createdAt; } ``` -#### App Settings Model +#### Supplier Model ```dart @HiveType(typeId: 4) -class AppSettings extends HiveObject { - @HiveField(0) - final String themeModeString; // 'light', 'dark', 'system' - - @HiveField(1) - final String language; - - @HiveField(2) - final String currency; - - @HiveField(3) - final double taxRate; - - @HiveField(4) - final String storeName; - - @HiveField(5) - final bool enableSync; - - @HiveField(6) - final DateTime lastSyncAt; +class Supplier extends HiveObject { + @HiveField(0) final String id; + @HiveField(1) final String name; + @HiveField(2) final String? contactPerson; + @HiveField(3) final String? email; + @HiveField(4) final String? phone; + @HiveField(5) final String? address; + @HiveField(6) final DateTime createdAt; +} +``` + +#### Order Model +```dart +@HiveType(typeId: 5) +class Order extends HiveObject { + @HiveField(0) final String id; + @HiveField(1) final String orderNumber; + @HiveField(2) final List items; + @HiveField(3) final double subtotal; + @HiveField(4) final double discount; + @HiveField(5) final double total; + @HiveField(6) final String paymentMethod; + @HiveField(7) final String orderStatus; // completed, cancelled + @HiveField(8) final DateTime createdAt; +} +``` + +#### Order Item Model +```dart +@HiveType(typeId: 6) +class OrderItem extends HiveObject { + @HiveField(0) final String id; + @HiveField(1) final String productId; + @HiveField(2) final String productName; + @HiveField(3) final String? variantId; + @HiveField(4) final String? variantName; + @HiveField(5) final double price; + @HiveField(6) final int quantity; + @HiveField(7) final double lineTotal; } ``` @@ -593,10 +895,12 @@ class AppSettings extends HiveObject { // Product card should display: - Product image (with placeholder/error handling) - Product name (2 lines max with ellipsis) -- Price (formatted with currency) -- Stock status badge (if low stock) +- Price (formatted with currency, show sale price if applicable) +- Stock status badge (in stock, low stock, out of stock) - Category badge +- Variant indicator (if product has variants) - Add to cart button/icon +- Quick edit button (for managers) - Tap to view details ``` @@ -610,23 +914,19 @@ class AppSettings extends HiveObject { - Tap to filter products by category ``` -### Cart Item Design -```dart -// Cart item should display: -- Product thumbnail -- Product name -- Unit price -- Quantity controls (+/-) -- Line total -- Remove button -``` +### Dashboard Charts +- Use **fl_chart** package for charts +- Revenue line chart with time axis +- Category pie chart with percentages +- Best sellers horizontal bar chart +- Responsive chart sizing +- Interactive tooltips -### Responsive Grid Layout -- **Mobile Portrait**: 2 columns (products/categories) -- **Mobile Landscape**: 3 columns -- **Tablet Portrait**: 3-4 columns -- **Tablet Landscape**: 4-5 columns -- Use `GridView.builder` with `SliverGridDelegate` +### Stock Level Indicators +- **High Stock**: Green badge +- **Medium Stock**: Orange badge +- **Low Stock**: Red badge with alert icon +- **Out of Stock**: Grey badge with warning ## Performance Optimization @@ -695,9 +995,12 @@ class DataSync extends _$DataSync { // Sync categories first await ref.read(categoriesProvider.notifier).syncCategories(); - // Then sync products + // Then sync products and variants await ref.read(productsProvider.notifier).syncProducts(); + // Sync suppliers + await ref.read(suppliersProvider.notifier).syncSuppliers(); + // Update last sync time await ref.read(settingsProvider.notifier).updateLastSync(); @@ -713,32 +1016,62 @@ class DataSync extends _$DataSync { ### Search Functionality - Real-time search with debouncing (300ms) -- Search by product name, description, category +- Search by product name, description, SKU, barcode +- Search across variants - Display search results count - Clear search button - Search history (optional) +### Filter System +- **Category Filter**: Filter by one or multiple categories +- **Price Range**: Min/max price slider +- **Stock Status**: In stock, low stock, out of stock +- **Supplier Filter**: Filter by supplier +- **Tag Filter**: Filter by tags +- **Variant Filter**: Show only products with variants + ### Cart Operations - Add to cart with quantity +- Add variant to cart with selected options - Update quantity with +/- buttons - Remove item with swipe or tap - Clear entire cart with confirmation - Calculate totals in real-time +- Apply discounts (cart-level or item-level) - Persist cart between sessions -### Transaction Processing -- Validate cart (not empty, stock available) -- Calculate subtotal, tax, discounts -- Process payment -- Save transaction to Hive -- Clear cart after successful transaction -- Generate receipt (optional) +### Inventory Operations +- **Alerts**: Show low-stock products +- **Batch Updates**: Update multiple products at once -### Category Filtering -- Filter products by selected category -- Show all products when no category selected -- Display active filter indicator -- Clear filter option +### Order Processing +- Validate cart (not empty, stock available) +- Select payment method +- Apply cart-level discount (optional) +- Calculate subtotal and discount +- Generate unique order number +- Save order to database +- Clear cart after successful order + +### CSV Import/Export +- **Import**: + - Download CSV template + - Select CSV file + - Parse and validate + - Preview import + - Confirm and import + - Handle errors gracefully +- **Export**: + - Select data to export (all, filtered, selected) + - Generate CSV + - Save/share file + +### Dashboard Analytics +- **Calculate Revenue**: Sum all transactions by period +- **Best Sellers**: Rank products by quantity/revenue +- **Category Performance**: Group sales by category +- **Trend Analysis**: Compare periods (today vs yesterday, month vs last month) +- **Charts**: Generate chart data from transactions ## Error Handling @@ -750,12 +1083,13 @@ class DataSync extends _$DataSync { ### Validation Errors - Validate cart before checkout -- Check product availability +- Check product availability and stock - Validate quantity inputs +- Validate price inputs (product form) - Display inline error messages ### Data Errors -- Handle empty states (no products, no categories) +- Handle empty states (no products, no categories, no orders) - Handle missing images gracefully - Validate data integrity on load - Provide fallback values @@ -768,6 +1102,8 @@ class DataSync extends _$DataSync { - Category filtering logic - Price formatting - Data validation +- Stock calculations +- Revenue calculations ### Widget Tests - Product card rendering @@ -775,13 +1111,14 @@ class DataSync extends _$DataSync { - Cart item rendering - Tab navigation - Search bar functionality +- Dashboard charts ### Integration Tests - Complete checkout flow -- Product search and filter -- Category selection and filtering -- Settings updates -- Sync operations +- Product CRUD operations +- Category management +- CSV import/export +- Order creation and history ## Development Workflow @@ -811,23 +1148,35 @@ class DataSync extends _$DataSync { ### Phase 1 - Current - βœ… Core POS functionality - βœ… Product and category management +- βœ… Product variants support - βœ… Basic cart and checkout +- βœ… Inventory tracking +- βœ… Stock alerts +- βœ… Order management +- βœ… Sales dashboard +- βœ… CSV import/export +- βœ… Batch operations - βœ… Settings management ### Phase 2 - Near Future -- πŸ”„ Product variants (size, color) -- πŸ”„ Discount codes and promotions -- πŸ”„ Multiple payment methods +- πŸ”„ Customer management +- πŸ”„ Loyalty program +- πŸ”„ Advanced reporting - πŸ”„ Receipt printing -- πŸ”„ Sales reports and analytics +- πŸ”„ Barcode scanning +- πŸ”„ Multiple payment methods +- πŸ”„ Staff management with roles +- πŸ”„ Promotional campaigns ### Phase 3 - Future -- πŸ“‹ Inventory management -- πŸ“‹ Customer management -- πŸ“‹ Multi-user support with roles +- πŸ“‹ Multi-store support - πŸ“‹ Cloud sync with backend -- πŸ“‹ Barcode scanning +- πŸ“‹ Integration with accounting software +- πŸ“‹ Advanced analytics and forecasting +- πŸ“‹ Supplier portal +- πŸ“‹ Purchase order management - πŸ“‹ Integration with payment gateways +- πŸ“‹ E-commerce integration --- diff --git a/lib/BARREL_EXPORTS_QUICK_REFERENCE.md b/docs/BARREL_EXPORTS_QUICK_REFERENCE.md similarity index 100% rename from lib/BARREL_EXPORTS_QUICK_REFERENCE.md rename to docs/BARREL_EXPORTS_QUICK_REFERENCE.md diff --git a/lib/EXPORTS_DOCUMENTATION.md b/docs/EXPORTS_DOCUMENTATION.md similarity index 100% rename from lib/EXPORTS_DOCUMENTATION.md rename to docs/EXPORTS_DOCUMENTATION.md diff --git a/lib/WIDGETS_DOCUMENTATION.md b/docs/WIDGETS_DOCUMENTATION.md similarity index 100% rename from lib/WIDGETS_DOCUMENTATION.md rename to docs/WIDGETS_DOCUMENTATION.md diff --git a/lib/core/providers/dio_client_provider.dart b/lib/core/providers/dio_client_provider.dart new file mode 100644 index 0000000..f59a274 --- /dev/null +++ b/lib/core/providers/dio_client_provider.dart @@ -0,0 +1,10 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../network/dio_client.dart'; + +part 'dio_client_provider.g.dart'; + +/// Provider for DioClient singleton +@Riverpod(keepAlive: true) +DioClient dioClient(Ref ref) { + return DioClient(); +} diff --git a/lib/core/providers/dio_client_provider.g.dart b/lib/core/providers/dio_client_provider.g.dart new file mode 100644 index 0000000..05074a4 --- /dev/null +++ b/lib/core/providers/dio_client_provider.g.dart @@ -0,0 +1,55 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'dio_client_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning +/// Provider for DioClient singleton + +@ProviderFor(dioClient) +const dioClientProvider = DioClientProvider._(); + +/// Provider for DioClient singleton + +final class DioClientProvider + extends $FunctionalProvider + with $Provider { + /// Provider for DioClient singleton + const DioClientProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'dioClientProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$dioClientHash(); + + @$internal + @override + $ProviderElement $createElement($ProviderPointer pointer) => + $ProviderElement(pointer); + + @override + DioClient create(Ref ref) { + return dioClient(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(DioClient value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$dioClientHash() => r'895f0dc2f8d5eab562ad65390e5c6d4a1f722b0d'; diff --git a/lib/core/providers/providers.dart b/lib/core/providers/providers.dart index f36e80c..2a8dce5 100644 --- a/lib/core/providers/providers.dart +++ b/lib/core/providers/providers.dart @@ -1,3 +1,4 @@ /// Export all core providers export 'network_info_provider.dart'; export 'sync_status_provider.dart'; +export 'dio_client_provider.dart'; diff --git a/lib/core/widgets/empty_state.dart b/lib/core/widgets/empty_state.dart index 2ac27fa..14cb011 100644 --- a/lib/core/widgets/empty_state.dart +++ b/lib/core/widgets/empty_state.dart @@ -27,7 +27,7 @@ class EmptyState extends StatelessWidget { children: [ Icon( icon ?? Icons.inbox_outlined, - size: 80, + size: 50, color: Theme.of(context).colorScheme.outline, ), const SizedBox(height: 24), diff --git a/lib/features/auth/presentation/providers/auth_provider.g.dart b/lib/features/auth/presentation/providers/auth_provider.g.dart index 9ac5ac6..e4b4c8d 100644 --- a/lib/features/auth/presentation/providers/auth_provider.g.dart +++ b/lib/features/auth/presentation/providers/auth_provider.g.dart @@ -234,7 +234,7 @@ final class AuthProvider extends $NotifierProvider { } } -String _$authHash() => r'4b053a7691f573316a8957577dd27a3ed73d89be'; +String _$authHash() => r'73c9e7b70799eba2904eb6fc65454332d4146a33'; /// Auth state notifier provider diff --git a/lib/features/products/data/datasources/product_remote_datasource.dart b/lib/features/products/data/datasources/product_remote_datasource.dart index c2b44a1..c0e09b5 100644 --- a/lib/features/products/data/datasources/product_remote_datasource.dart +++ b/lib/features/products/data/datasources/product_remote_datasource.dart @@ -1,12 +1,19 @@ import '../models/product_model.dart'; import '../../../../core/network/dio_client.dart'; import '../../../../core/constants/api_constants.dart'; +import '../../../../core/errors/exceptions.dart'; /// Product remote data source using API abstract class ProductRemoteDataSource { - Future> getAllProducts(); + Future> getAllProducts({ + int page = 1, + int limit = 20, + String? categoryId, + String? search, + }); Future getProductById(String id); - Future> searchProducts(String query); + Future> searchProducts(String query, {int page = 1, int limit = 20}); + Future> getProductsByCategory(String categoryId, {int page = 1, int limit = 20}); } class ProductRemoteDataSourceImpl implements ProductRemoteDataSource { @@ -15,25 +22,107 @@ class ProductRemoteDataSourceImpl implements ProductRemoteDataSource { ProductRemoteDataSourceImpl(this.client); @override - Future> getAllProducts() async { - final response = await client.get(ApiConstants.products); - final List data = response.data['products'] ?? []; - return data.map((json) => ProductModel.fromJson(json)).toList(); + Future> getAllProducts({ + int page = 1, + int limit = 20, + String? categoryId, + String? search, + }) async { + try { + final queryParams = { + 'page': page, + 'limit': limit, + }; + + if (categoryId != null) { + queryParams['categoryId'] = categoryId; + } + + if (search != null && search.isNotEmpty) { + queryParams['search'] = search; + } + + final response = await client.get( + ApiConstants.products, + queryParameters: queryParams, + ); + + // API returns: { success: true, data: [...products...], meta: {...} } + if (response.data['success'] == true) { + final List data = response.data['data'] ?? []; + return data.map((json) => ProductModel.fromJson(json)).toList(); + } else { + throw ServerException(response.data['message'] ?? 'Failed to fetch products'); + } + } catch (e) { + if (e is ServerException) rethrow; + throw ServerException('Failed to fetch products: $e'); + } } @override Future getProductById(String id) async { - final response = await client.get(ApiConstants.productById(id)); - return ProductModel.fromJson(response.data); + try { + final response = await client.get(ApiConstants.productById(id)); + + // API returns: { success: true, data: {...product...} } + if (response.data['success'] == true) { + return ProductModel.fromJson(response.data['data']); + } else { + throw ServerException(response.data['message'] ?? 'Product not found'); + } + } catch (e) { + if (e is ServerException) rethrow; + throw ServerException('Failed to fetch product: $e'); + } } @override - Future> searchProducts(String query) async { - final response = await client.get( - ApiConstants.searchProducts, - queryParameters: {'q': query}, - ); - final List data = response.data['products'] ?? []; - return data.map((json) => ProductModel.fromJson(json)).toList(); + Future> searchProducts(String query, {int page = 1, int limit = 20}) async { + try { + final response = await client.get( + ApiConstants.searchProducts, + queryParameters: { + 'q': query, + 'page': page, + 'limit': limit, + }, + ); + + // API returns: { success: true, data: [...products...], meta: {...} } + if (response.data['success'] == true) { + final List data = response.data['data'] ?? []; + return data.map((json) => ProductModel.fromJson(json)).toList(); + } else { + throw ServerException(response.data['message'] ?? 'Failed to search products'); + } + } catch (e) { + if (e is ServerException) rethrow; + throw ServerException('Failed to search products: $e'); + } + } + + @override + Future> getProductsByCategory(String categoryId, {int page = 1, int limit = 20}) async { + try { + final response = await client.get( + ApiConstants.productsByCategory(categoryId), + queryParameters: { + 'page': page, + 'limit': limit, + }, + ); + + // API returns: { success: true, data: [...products...], meta: {...} } + if (response.data['success'] == true) { + final List data = response.data['data'] ?? []; + return data.map((json) => ProductModel.fromJson(json)).toList(); + } else { + throw ServerException(response.data['message'] ?? 'Failed to fetch products by category'); + } + } catch (e) { + if (e is ServerException) rethrow; + throw ServerException('Failed to fetch products by category: $e'); + } } } diff --git a/lib/features/products/data/models/product_model.dart b/lib/features/products/data/models/product_model.dart index 5be1108..f1f24a4 100644 --- a/lib/features/products/data/models/product_model.dart +++ b/lib/features/products/data/models/product_model.dart @@ -86,12 +86,12 @@ class ProductModel extends HiveObject { return ProductModel( id: json['id'] as String, name: json['name'] as String, - description: json['description'] as String, + description: json['description'] as String? ?? '', price: (json['price'] as num).toDouble(), imageUrl: json['imageUrl'] as String?, categoryId: json['categoryId'] as String, - stockQuantity: json['stockQuantity'] as int, - isAvailable: json['isAvailable'] as bool, + stockQuantity: json['stockQuantity'] as int? ?? 0, + isAvailable: json['isAvailable'] as bool? ?? true, createdAt: DateTime.parse(json['createdAt'] as String), updatedAt: DateTime.parse(json['updatedAt'] as String), ); diff --git a/lib/features/products/data/providers/product_providers.dart b/lib/features/products/data/providers/product_providers.dart new file mode 100644 index 0000000..d677209 --- /dev/null +++ b/lib/features/products/data/providers/product_providers.dart @@ -0,0 +1,43 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:hive_ce/hive.dart'; +import '../datasources/product_local_datasource.dart'; +import '../datasources/product_remote_datasource.dart'; +import '../repositories/product_repository_impl.dart'; +import '../models/product_model.dart'; +import '../../domain/repositories/product_repository.dart'; +import '../../../../core/providers/providers.dart'; +import '../../../../core/constants/storage_constants.dart'; + +part 'product_providers.g.dart'; + +/// Provider for product Hive box +@riverpod +Box productBox(Ref ref) { + return Hive.box(StorageConstants.productsBox); +} + +/// Provider for product local data source +@riverpod +ProductLocalDataSource productLocalDataSource(Ref ref) { + final box = ref.watch(productBoxProvider); + return ProductLocalDataSourceImpl(box); +} + +/// Provider for product remote data source +@riverpod +ProductRemoteDataSource productRemoteDataSource(Ref ref) { + final dioClient = ref.watch(dioClientProvider); + return ProductRemoteDataSourceImpl(dioClient); +} + +/// Provider for product repository +@riverpod +ProductRepository productRepository(Ref ref) { + final localDataSource = ref.watch(productLocalDataSourceProvider); + final remoteDataSource = ref.watch(productRemoteDataSourceProvider); + + return ProductRepositoryImpl( + localDataSource: localDataSource, + remoteDataSource: remoteDataSource, + ); +} diff --git a/lib/features/products/data/providers/product_providers.g.dart b/lib/features/products/data/providers/product_providers.g.dart new file mode 100644 index 0000000..1baa85b --- /dev/null +++ b/lib/features/products/data/providers/product_providers.g.dart @@ -0,0 +1,219 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'product_providers.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning +/// Provider for product Hive box + +@ProviderFor(productBox) +const productBoxProvider = ProductBoxProvider._(); + +/// Provider for product Hive box + +final class ProductBoxProvider + extends + $FunctionalProvider< + Box, + Box, + Box + > + with $Provider> { + /// Provider for product Hive box + const ProductBoxProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'productBoxProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$productBoxHash(); + + @$internal + @override + $ProviderElement> $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + Box create(Ref ref) { + return productBox(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(Box value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider>(value), + ); + } +} + +String _$productBoxHash() => r'68cd21ea28cfc716f34daef17849a0393cdb2b80'; + +/// Provider for product local data source + +@ProviderFor(productLocalDataSource) +const productLocalDataSourceProvider = ProductLocalDataSourceProvider._(); + +/// Provider for product local data source + +final class ProductLocalDataSourceProvider + extends + $FunctionalProvider< + ProductLocalDataSource, + ProductLocalDataSource, + ProductLocalDataSource + > + with $Provider { + /// Provider for product local data source + const ProductLocalDataSourceProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'productLocalDataSourceProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$productLocalDataSourceHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + ProductLocalDataSource create(Ref ref) { + return productLocalDataSource(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(ProductLocalDataSource value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$productLocalDataSourceHash() => + r'ef4673055777e8dc8a8419a80548b319789d99f9'; + +/// Provider for product remote data source + +@ProviderFor(productRemoteDataSource) +const productRemoteDataSourceProvider = ProductRemoteDataSourceProvider._(); + +/// Provider for product remote data source + +final class ProductRemoteDataSourceProvider + extends + $FunctionalProvider< + ProductRemoteDataSource, + ProductRemoteDataSource, + ProductRemoteDataSource + > + with $Provider { + /// Provider for product remote data source + const ProductRemoteDataSourceProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'productRemoteDataSourceProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$productRemoteDataSourceHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + ProductRemoteDataSource create(Ref ref) { + return productRemoteDataSource(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(ProductRemoteDataSource value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$productRemoteDataSourceHash() => + r'954798907bb0c9baade27b84eaba612a5dec8f68'; + +/// Provider for product repository + +@ProviderFor(productRepository) +const productRepositoryProvider = ProductRepositoryProvider._(); + +/// Provider for product repository + +final class ProductRepositoryProvider + extends + $FunctionalProvider< + ProductRepository, + ProductRepository, + ProductRepository + > + with $Provider { + /// Provider for product repository + const ProductRepositoryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'productRepositoryProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$productRepositoryHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + ProductRepository create(Ref ref) { + return productRepository(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(ProductRepository value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$productRepositoryHash() => r'7c5c5b274ce459add6449c29be822ea04503d3dc'; diff --git a/lib/features/products/presentation/pages/products_page.dart b/lib/features/products/presentation/pages/products_page.dart index 14d98f5..3c44cf8 100644 --- a/lib/features/products/presentation/pages/products_page.dart +++ b/lib/features/products/presentation/pages/products_page.dart @@ -25,6 +25,13 @@ class _ProductsPageState extends ConsumerState { final selectedCategory = ref.watch(product_providers.selectedCategoryProvider); final productsAsync = ref.watch(productsProvider); + // Debug: Log product loading state + productsAsync.whenOrNull( + data: (products) => debugPrint('Products loaded: ${products.length} items'), + loading: () => debugPrint('Products loading...'), + error: (error, stack) => debugPrint('Products error: $error'), + ); + // Get filtered products from the provider final filteredProducts = productsAsync.when( data: (products) => products, @@ -168,12 +175,14 @@ class _ProductsPageState extends ConsumerState { ), ), ), - body: RefreshIndicator( - onRefresh: () async { - await ref.refresh(productsProvider.future); - await ref.refresh(categoriesProvider.future); - }, - child: Column( + body: productsAsync.when( + data: (products) => RefreshIndicator( + onRefresh: () async { + // Force sync with API + await ref.read(productsProvider.notifier).syncProducts(); + await ref.refresh(categoriesProvider.future); + }, + child: Column( children: [ // Results count if (filteredProducts.isNotEmpty) @@ -194,6 +203,23 @@ class _ProductsPageState extends ConsumerState { ), ], ), + ), + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.error_outline, size: 48, color: Colors.red), + const SizedBox(height: 16), + Text('Error loading products: $error'), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () => ref.refresh(productsProvider), + child: const Text('Retry'), + ), + ], + ), + ), ), ); } diff --git a/lib/features/products/presentation/providers/products_provider.dart b/lib/features/products/presentation/providers/products_provider.dart index b60795d..67f64c2 100644 --- a/lib/features/products/presentation/providers/products_provider.dart +++ b/lib/features/products/presentation/providers/products_provider.dart @@ -1,32 +1,92 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../domain/entities/product.dart'; +import '../../data/providers/product_providers.dart'; +import '../../../../core/providers/providers.dart'; part 'products_provider.g.dart'; -/// Provider for products list +/// Provider for products list with API-first approach @riverpod class Products extends _$Products { @override Future> build() async { - // TODO: Implement with repository - return []; + // API-first: Try to load from API first + final repository = ref.watch(productRepositoryProvider); + final networkInfo = ref.watch(networkInfoProvider); + + // Check if online + final isConnected = await networkInfo.isConnected; + + if (isConnected) { + // Try API first + try { + final syncResult = await repository.syncProducts(); + return syncResult.fold( + (failure) { + // API failed, fallback to cache + print('API failed, falling back to cache: ${failure.message}'); + return _loadFromCache(); + }, + (products) => products, + ); + } catch (e) { + // API error, fallback to cache + print('API error, falling back to cache: $e'); + return _loadFromCache(); + } + } else { + // Offline, load from cache + print('Offline, loading from cache'); + return _loadFromCache(); + } } + /// Load products from local cache + Future> _loadFromCache() async { + final repository = ref.read(productRepositoryProvider); + final result = await repository.getAllProducts(); + + return result.fold( + (failure) { + print('Cache load failed: ${failure.message}'); + return []; + }, + (products) => products, + ); + } + + /// Refresh products from local storage Future refresh() async { - // TODO: Implement refresh logic state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { - // Fetch products from repository - return []; + final repository = ref.read(productRepositoryProvider); + final result = await repository.getAllProducts(); + + return result.fold( + (failure) => throw Exception(failure.message), + (products) => products, + ); }); } + /// Sync products from API and update local storage Future syncProducts() async { - // TODO: Implement sync logic with remote data source + final networkInfo = ref.read(networkInfoProvider); + final isConnected = await networkInfo.isConnected; + + if (!isConnected) { + throw Exception('No internet connection'); + } + state = const AsyncValue.loading(); state = await AsyncValue.guard(() async { - // Sync products from API - return []; + final repository = ref.read(productRepositoryProvider); + final result = await repository.syncProducts(); + + return result.fold( + (failure) => throw Exception(failure.message), + (products) => products, + ); }); } } diff --git a/lib/features/products/presentation/providers/products_provider.g.dart b/lib/features/products/presentation/providers/products_provider.g.dart index 8477483..570aa5f 100644 --- a/lib/features/products/presentation/providers/products_provider.g.dart +++ b/lib/features/products/presentation/providers/products_provider.g.dart @@ -8,15 +8,15 @@ part of 'products_provider.dart'; // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint, type=warning -/// Provider for products list +/// Provider for products list with API-first approach @ProviderFor(Products) const productsProvider = ProductsProvider._(); -/// Provider for products list +/// Provider for products list with API-first approach final class ProductsProvider extends $AsyncNotifierProvider> { - /// Provider for products list + /// Provider for products list with API-first approach const ProductsProvider._() : super( from: null, @@ -36,9 +36,9 @@ final class ProductsProvider Products create() => Products(); } -String _$productsHash() => r'9e1d3aaa1d9cf0b4ff03fdfaf4512a7a15336d51'; +String _$productsHash() => r'0ff8c2de46bb4b1e29678cc811ec121c9fb4c8eb'; -/// Provider for products list +/// Provider for products list with API-first approach abstract class _$Products extends $AsyncNotifier> { FutureOr> build(); diff --git a/lib/main.dart b/lib/main.dart index 85462c8..5f0da0c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_ce_flutter/hive_flutter.dart'; import 'app.dart'; +import 'core/constants/storage_constants.dart'; +import 'features/products/data/models/product_model.dart'; +import 'features/categories/data/models/category_model.dart'; +import 'features/home/data/models/cart_item_model.dart'; +import 'features/home/data/models/transaction_model.dart'; +import 'features/settings/data/models/app_settings_model.dart'; /// Main entry point of the application void main() async { @@ -12,18 +18,18 @@ void main() async { await Hive.initFlutter(); // Register Hive adapters - // TODO: Register adapters after running code generation - // Hive.registerAdapter(ProductModelAdapter()); - // Hive.registerAdapter(CategoryModelAdapter()); - // Hive.registerAdapter(CartItemModelAdapter()); - // Hive.registerAdapter(AppSettingsModelAdapter()); + Hive.registerAdapter(ProductModelAdapter()); + Hive.registerAdapter(CategoryModelAdapter()); + Hive.registerAdapter(CartItemModelAdapter()); + Hive.registerAdapter(TransactionModelAdapter()); + Hive.registerAdapter(AppSettingsModelAdapter()); // Open Hive boxes - // TODO: Open boxes after registering adapters - // await Hive.openBox(StorageConstants.productsBox); - // await Hive.openBox(StorageConstants.categoriesBox); - // await Hive.openBox(StorageConstants.cartBox); - // await Hive.openBox(StorageConstants.settingsBox); + await Hive.openBox(StorageConstants.productsBox); + await Hive.openBox(StorageConstants.categoriesBox); + await Hive.openBox(StorageConstants.cartBox); + await Hive.openBox(StorageConstants.transactionsBox); + await Hive.openBox(StorageConstants.settingsBox); // Run the app with Riverpod (no GetIt needed - using Riverpod for DI) runApp(