Add Kell Creations operations app foundation with feature slices and WooCommerce read-only integration #1
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:feature_inventory/feature_inventory.dart';
|
import 'package:feature_inventory/feature_inventory.dart';
|
||||||
|
import 'package:feature_orders/feature_orders.dart';
|
||||||
import 'package:feature_wordpress/feature_wordpress.dart';
|
import 'package:feature_wordpress/feature_wordpress.dart';
|
||||||
|
|
||||||
/// Holds the concrete service implementations used by the app.
|
/// Holds the concrete service implementations used by the app.
|
||||||
|
|
@ -8,14 +9,20 @@ import 'package:feature_wordpress/feature_wordpress.dart';
|
||||||
/// production backends are ready.
|
/// production backends are ready.
|
||||||
class AppServices {
|
class AppServices {
|
||||||
final InventoryRepository inventoryRepository;
|
final InventoryRepository inventoryRepository;
|
||||||
|
final OrdersRepository ordersRepository;
|
||||||
final ProductPublishingRepository productPublishingRepository;
|
final ProductPublishingRepository productPublishingRepository;
|
||||||
|
|
||||||
const AppServices({required this.inventoryRepository, required this.productPublishingRepository});
|
const AppServices({
|
||||||
|
required this.inventoryRepository,
|
||||||
|
required this.ordersRepository,
|
||||||
|
required this.productPublishingRepository,
|
||||||
|
});
|
||||||
|
|
||||||
/// Creates an [AppServices] backed by fake, in-memory repositories.
|
/// Creates an [AppServices] backed by fake, in-memory repositories.
|
||||||
factory AppServices.fake() {
|
factory AppServices.fake() {
|
||||||
return AppServices(
|
return AppServices(
|
||||||
inventoryRepository: FakeInventoryRepository(),
|
inventoryRepository: FakeInventoryRepository(),
|
||||||
|
ordersRepository: FakeOrdersRepository(),
|
||||||
productPublishingRepository: FakeProductPublishingRepository(),
|
productPublishingRepository: FakeProductPublishingRepository(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:feature_inventory/feature_inventory.dart';
|
import 'package:feature_inventory/feature_inventory.dart';
|
||||||
|
import 'package:feature_orders/feature_orders.dart';
|
||||||
import 'package:feature_wordpress/feature_wordpress.dart';
|
import 'package:feature_wordpress/feature_wordpress.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
|
@ -6,7 +7,6 @@ import '../composition/app_scope.dart';
|
||||||
import '../pages/dashboard_page.dart';
|
import '../pages/dashboard_page.dart';
|
||||||
import '../pages/finance_placeholder_page.dart';
|
import '../pages/finance_placeholder_page.dart';
|
||||||
import '../pages/integrations_placeholder_page.dart';
|
import '../pages/integrations_placeholder_page.dart';
|
||||||
import '../pages/orders_placeholder_page.dart';
|
|
||||||
import '../pages/policy_placeholder_page.dart';
|
import '../pages/policy_placeholder_page.dart';
|
||||||
import '../shell/app_shell.dart';
|
import '../shell/app_shell.dart';
|
||||||
|
|
||||||
|
|
@ -50,10 +50,10 @@ abstract final class AppRoutes {
|
||||||
case orders:
|
case orders:
|
||||||
return _buildRoute(
|
return _buildRoute(
|
||||||
settings,
|
settings,
|
||||||
(context) => const AppShell(
|
(context) => AppShell(
|
||||||
selectedRoute: orders,
|
selectedRoute: orders,
|
||||||
title: 'Orders',
|
title: 'Orders',
|
||||||
child: OrdersPlaceholderPage(),
|
child: OrdersPage(repository: AppScope.of(context).ordersRepository),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
case finance:
|
case finance:
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,13 @@ packages:
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
feature_orders:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../../packages/feature_orders"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
feature_wordpress:
|
feature_wordpress:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ dependencies:
|
||||||
path: ../../packages/design_system
|
path: ../../packages/design_system
|
||||||
feature_inventory:
|
feature_inventory:
|
||||||
path: ../../packages/feature_inventory
|
path: ../../packages/feature_inventory
|
||||||
|
feature_orders:
|
||||||
|
path: ../../packages/feature_orders
|
||||||
feature_wordpress:
|
feature_wordpress:
|
||||||
path: ../../packages/feature_wordpress
|
path: ../../packages/feature_wordpress
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,178 @@
|
||||||
|
{
|
||||||
|
"configVersion": 2,
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "async",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/async-2.13.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "boolean_selector",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/boolean_selector-2.1.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "characters",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/characters-1.4.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clock",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/clock-1.1.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collection",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/collection-1.19.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "design_system",
|
||||||
|
"rootUri": "../../design_system",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fake_async",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/fake_async-1.3.3",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter",
|
||||||
|
"rootUri": "file:///D:/develop/flutter/packages/flutter",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter_lints",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_lints-6.0.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter_test",
|
||||||
|
"rootUri": "file:///D:/develop/flutter/packages/flutter_test",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker-11.0.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker_flutter_testing",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.10",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker_testing",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker_testing-3.0.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lints",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/lints-6.1.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "matcher",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/matcher-0.12.19",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "material_color_utilities",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/material_color_utilities-0.13.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "meta",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/meta-1.17.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "path",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/path-1.9.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sky_engine",
|
||||||
|
"rootUri": "file:///D:/develop/flutter/bin/cache/pkg/sky_engine",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "source_span",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/source_span-1.10.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stack_trace",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/stack_trace-1.12.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stream_channel",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/stream_channel-2.1.4",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "string_scanner",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/string_scanner-1.4.1",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "term_glyph",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/term_glyph-1.2.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "test_api",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/test_api-0.7.10",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vector_math",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/vector_math-2.2.0",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vm_service",
|
||||||
|
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/vm_service-15.0.2",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "feature_orders",
|
||||||
|
"rootUri": "../",
|
||||||
|
"packageUri": "lib/",
|
||||||
|
"languageVersion": "3.11"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"generator": "pub",
|
||||||
|
"generatorVersion": "3.11.4",
|
||||||
|
"flutterRoot": "file:///D:/develop/flutter",
|
||||||
|
"flutterVersion": "3.41.6",
|
||||||
|
"pubCache": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,232 @@
|
||||||
|
{
|
||||||
|
"roots": [
|
||||||
|
"feature_orders"
|
||||||
|
],
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "feature_orders",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": [
|
||||||
|
"design_system",
|
||||||
|
"flutter"
|
||||||
|
],
|
||||||
|
"devDependencies": [
|
||||||
|
"flutter_lints",
|
||||||
|
"flutter_test"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter_lints",
|
||||||
|
"version": "6.0.0",
|
||||||
|
"dependencies": [
|
||||||
|
"lints"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter_test",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": [
|
||||||
|
"clock",
|
||||||
|
"collection",
|
||||||
|
"fake_async",
|
||||||
|
"flutter",
|
||||||
|
"leak_tracker_flutter_testing",
|
||||||
|
"matcher",
|
||||||
|
"meta",
|
||||||
|
"path",
|
||||||
|
"stack_trace",
|
||||||
|
"stream_channel",
|
||||||
|
"test_api",
|
||||||
|
"vector_math"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "design_system",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": [
|
||||||
|
"flutter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flutter",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": [
|
||||||
|
"characters",
|
||||||
|
"collection",
|
||||||
|
"material_color_utilities",
|
||||||
|
"meta",
|
||||||
|
"sky_engine",
|
||||||
|
"vector_math"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lints",
|
||||||
|
"version": "6.1.0",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stream_channel",
|
||||||
|
"version": "2.1.4",
|
||||||
|
"dependencies": [
|
||||||
|
"async"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "meta",
|
||||||
|
"version": "1.17.0",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "collection",
|
||||||
|
"version": "1.19.1",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker_flutter_testing",
|
||||||
|
"version": "3.0.10",
|
||||||
|
"dependencies": [
|
||||||
|
"flutter",
|
||||||
|
"leak_tracker",
|
||||||
|
"leak_tracker_testing",
|
||||||
|
"matcher",
|
||||||
|
"meta"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vector_math",
|
||||||
|
"version": "2.2.0",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stack_trace",
|
||||||
|
"version": "1.12.1",
|
||||||
|
"dependencies": [
|
||||||
|
"path"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "clock",
|
||||||
|
"version": "1.1.2",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fake_async",
|
||||||
|
"version": "1.3.3",
|
||||||
|
"dependencies": [
|
||||||
|
"clock",
|
||||||
|
"collection"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "path",
|
||||||
|
"version": "1.9.1",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "matcher",
|
||||||
|
"version": "0.12.19",
|
||||||
|
"dependencies": [
|
||||||
|
"async",
|
||||||
|
"meta",
|
||||||
|
"stack_trace",
|
||||||
|
"term_glyph",
|
||||||
|
"test_api"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "test_api",
|
||||||
|
"version": "0.7.10",
|
||||||
|
"dependencies": [
|
||||||
|
"async",
|
||||||
|
"boolean_selector",
|
||||||
|
"collection",
|
||||||
|
"meta",
|
||||||
|
"source_span",
|
||||||
|
"stack_trace",
|
||||||
|
"stream_channel",
|
||||||
|
"string_scanner",
|
||||||
|
"term_glyph"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sky_engine",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "material_color_utilities",
|
||||||
|
"version": "0.13.0",
|
||||||
|
"dependencies": [
|
||||||
|
"collection"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "characters",
|
||||||
|
"version": "1.4.1",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "async",
|
||||||
|
"version": "2.13.1",
|
||||||
|
"dependencies": [
|
||||||
|
"collection",
|
||||||
|
"meta"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker_testing",
|
||||||
|
"version": "3.0.2",
|
||||||
|
"dependencies": [
|
||||||
|
"leak_tracker",
|
||||||
|
"matcher",
|
||||||
|
"meta"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leak_tracker",
|
||||||
|
"version": "11.0.2",
|
||||||
|
"dependencies": [
|
||||||
|
"clock",
|
||||||
|
"collection",
|
||||||
|
"meta",
|
||||||
|
"path",
|
||||||
|
"vm_service"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "term_glyph",
|
||||||
|
"version": "1.2.2",
|
||||||
|
"dependencies": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "string_scanner",
|
||||||
|
"version": "1.4.1",
|
||||||
|
"dependencies": [
|
||||||
|
"source_span"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "source_span",
|
||||||
|
"version": "1.10.2",
|
||||||
|
"dependencies": [
|
||||||
|
"collection",
|
||||||
|
"path",
|
||||||
|
"term_glyph"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "boolean_selector",
|
||||||
|
"version": "2.1.2",
|
||||||
|
"dependencies": [
|
||||||
|
"source_span",
|
||||||
|
"string_scanner"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "vm_service",
|
||||||
|
"version": "15.0.2",
|
||||||
|
"dependencies": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configVersion": 1
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
3.41.6
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"format-version":[1,0,0],"native-assets":{}}
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
[]
|
||||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
{"format-version":[1,0,0],"native-assets":{}}
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
||||||
|
library;
|
||||||
|
|
||||||
|
export 'src/data/fake_orders_repository.dart';
|
||||||
|
export 'src/domain/order.dart';
|
||||||
|
export 'src/domain/order_item.dart';
|
||||||
|
export 'src/domain/order_status.dart';
|
||||||
|
export 'src/domain/orders_repository.dart';
|
||||||
|
export 'src/presentation/orders_page.dart';
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import '../domain/order.dart';
|
||||||
|
import '../domain/orders_repository.dart';
|
||||||
|
|
||||||
|
/// Use case: retrieve all orders from the repository.
|
||||||
|
class GetOrders {
|
||||||
|
final OrdersRepository repository;
|
||||||
|
|
||||||
|
GetOrders(this.repository);
|
||||||
|
|
||||||
|
Future<List<Order>> call() => repository.getOrders();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import '../domain/order.dart';
|
||||||
|
import 'get_orders.dart';
|
||||||
|
|
||||||
|
/// Controller that manages the orders workspace state.
|
||||||
|
class OrdersController extends ChangeNotifier {
|
||||||
|
final GetOrders _getOrders;
|
||||||
|
|
||||||
|
OrdersController(this._getOrders);
|
||||||
|
|
||||||
|
bool isLoading = false;
|
||||||
|
List<Order> orders = [];
|
||||||
|
Order? selectedOrder;
|
||||||
|
Object? error;
|
||||||
|
|
||||||
|
/// Loads all orders.
|
||||||
|
Future<void> load() async {
|
||||||
|
isLoading = true;
|
||||||
|
error = null;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
try {
|
||||||
|
orders = await _getOrders();
|
||||||
|
// Auto-select the first order if nothing is selected.
|
||||||
|
selectedOrder ??= orders.isNotEmpty ? orders.first : null;
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
} finally {
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Selects an order for detail view.
|
||||||
|
void selectOrder(Order order) {
|
||||||
|
selectedOrder = order;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
import '../domain/order.dart';
|
||||||
|
import '../domain/order_item.dart';
|
||||||
|
import '../domain/order_status.dart';
|
||||||
|
import '../domain/orders_repository.dart';
|
||||||
|
|
||||||
|
/// Stubbed implementation of [OrdersRepository] with sample
|
||||||
|
/// Kell Creations orders. No real WooCommerce or shipping API calls are made.
|
||||||
|
class FakeOrdersRepository implements OrdersRepository {
|
||||||
|
final List<Order> _orders = [
|
||||||
|
Order(
|
||||||
|
id: 'KC-1001',
|
||||||
|
customerName: 'Sarah Mitchell',
|
||||||
|
customerEmail: 'sarah.mitchell@example.com',
|
||||||
|
orderDate: DateTime(2026, 4, 1),
|
||||||
|
status: OrderStatus.delivered,
|
||||||
|
shippingAddress: '123 Maple St, Asheville, NC 28801',
|
||||||
|
items: const [
|
||||||
|
OrderItem(
|
||||||
|
productName: 'Floral Bowl Cozy',
|
||||||
|
sku: 'BC-FLR-001',
|
||||||
|
quantity: 2,
|
||||||
|
unitPrice: 12.99,
|
||||||
|
),
|
||||||
|
OrderItem(
|
||||||
|
productName: 'Citrus Coaster Set',
|
||||||
|
sku: 'CS-CIT-002',
|
||||||
|
quantity: 1,
|
||||||
|
unitPrice: 16.50,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Order(
|
||||||
|
id: 'KC-1002',
|
||||||
|
customerName: 'James Thornton',
|
||||||
|
customerEmail: 'james.thornton@example.com',
|
||||||
|
orderDate: DateTime(2026, 4, 2),
|
||||||
|
status: OrderStatus.shipped,
|
||||||
|
shippingAddress: '456 Oak Ave, Knoxville, TN 37902',
|
||||||
|
items: const [
|
||||||
|
OrderItem(
|
||||||
|
productName: 'Ocean Nightlight',
|
||||||
|
sku: 'NL-OCN-003',
|
||||||
|
quantity: 1,
|
||||||
|
unitPrice: 19.99,
|
||||||
|
),
|
||||||
|
OrderItem(
|
||||||
|
productName: 'Sublimated Slate Coaster',
|
||||||
|
sku: 'SC-SUB-006',
|
||||||
|
quantity: 3,
|
||||||
|
unitPrice: 14.99,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Order(
|
||||||
|
id: 'KC-1003',
|
||||||
|
customerName: 'Emily Chen',
|
||||||
|
customerEmail: 'emily.chen@example.com',
|
||||||
|
orderDate: DateTime(2026, 4, 3),
|
||||||
|
status: OrderStatus.processing,
|
||||||
|
shippingAddress: '789 Pine Rd, Charlotte, NC 28202',
|
||||||
|
items: const [
|
||||||
|
OrderItem(
|
||||||
|
productName: 'Fabric Jar Gripper',
|
||||||
|
sku: 'JG-BLU-004',
|
||||||
|
quantity: 4,
|
||||||
|
unitPrice: 8.50,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Order(
|
||||||
|
id: 'KC-1004',
|
||||||
|
customerName: 'David Park',
|
||||||
|
customerEmail: 'david.park@example.com',
|
||||||
|
orderDate: DateTime(2026, 4, 3),
|
||||||
|
status: OrderStatus.pending,
|
||||||
|
shippingAddress: '321 Birch Ln, Greenville, SC 29601',
|
||||||
|
items: const [
|
||||||
|
OrderItem(
|
||||||
|
productName: 'Skillet Handle Sleeve',
|
||||||
|
sku: 'SH-SUN-005',
|
||||||
|
quantity: 2,
|
||||||
|
unitPrice: 10.99,
|
||||||
|
),
|
||||||
|
OrderItem(
|
||||||
|
productName: 'Floral Bowl Cozy',
|
||||||
|
sku: 'BC-FLR-001',
|
||||||
|
quantity: 1,
|
||||||
|
unitPrice: 12.99,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Order(
|
||||||
|
id: 'KC-1005',
|
||||||
|
customerName: 'Rachel Adams',
|
||||||
|
customerEmail: 'rachel.adams@example.com',
|
||||||
|
orderDate: DateTime(2026, 3, 28),
|
||||||
|
status: OrderStatus.cancelled,
|
||||||
|
shippingAddress: '654 Elm St, Richmond, VA 23220',
|
||||||
|
items: const [
|
||||||
|
OrderItem(
|
||||||
|
productName: 'Citrus Coaster Set',
|
||||||
|
sku: 'CS-CIT-002',
|
||||||
|
quantity: 2,
|
||||||
|
unitPrice: 16.50,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Order(
|
||||||
|
id: 'KC-1006',
|
||||||
|
customerName: 'Maria Gonzalez',
|
||||||
|
customerEmail: 'maria.gonzalez@example.com',
|
||||||
|
orderDate: DateTime(2026, 4, 4),
|
||||||
|
status: OrderStatus.pending,
|
||||||
|
shippingAddress: '987 Cedar Dr, Atlanta, GA 30301',
|
||||||
|
items: const [
|
||||||
|
OrderItem(
|
||||||
|
productName: 'Ocean Nightlight',
|
||||||
|
sku: 'NL-OCN-003',
|
||||||
|
quantity: 1,
|
||||||
|
unitPrice: 19.99,
|
||||||
|
),
|
||||||
|
OrderItem(
|
||||||
|
productName: 'Fabric Jar Gripper',
|
||||||
|
sku: 'JG-BLU-004',
|
||||||
|
quantity: 2,
|
||||||
|
unitPrice: 8.50,
|
||||||
|
),
|
||||||
|
OrderItem(
|
||||||
|
productName: 'Sublimated Slate Coaster',
|
||||||
|
sku: 'SC-SUB-006',
|
||||||
|
quantity: 1,
|
||||||
|
unitPrice: 14.99,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Order>> getOrders() async {
|
||||||
|
// Simulate network latency.
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 300));
|
||||||
|
return List.unmodifiable(_orders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import 'order_item.dart';
|
||||||
|
import 'order_status.dart';
|
||||||
|
|
||||||
|
/// A customer order placed through the Kell Creations store.
|
||||||
|
class Order {
|
||||||
|
final String id;
|
||||||
|
final String customerName;
|
||||||
|
final String customerEmail;
|
||||||
|
final DateTime orderDate;
|
||||||
|
final OrderStatus status;
|
||||||
|
final List<OrderItem> items;
|
||||||
|
final String shippingAddress;
|
||||||
|
|
||||||
|
const Order({
|
||||||
|
required this.id,
|
||||||
|
required this.customerName,
|
||||||
|
required this.customerEmail,
|
||||||
|
required this.orderDate,
|
||||||
|
required this.status,
|
||||||
|
required this.items,
|
||||||
|
required this.shippingAddress,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The total value of the order.
|
||||||
|
double get total => items.fold(0, (sum, item) => sum + item.lineTotal);
|
||||||
|
|
||||||
|
/// The number of individual items in the order.
|
||||||
|
int get itemCount => items.fold(0, (sum, item) => sum + item.quantity);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
/// A single line item within an [Order].
|
||||||
|
class OrderItem {
|
||||||
|
final String productName;
|
||||||
|
final String sku;
|
||||||
|
final int quantity;
|
||||||
|
final double unitPrice;
|
||||||
|
|
||||||
|
const OrderItem({
|
||||||
|
required this.productName,
|
||||||
|
required this.sku,
|
||||||
|
required this.quantity,
|
||||||
|
required this.unitPrice,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The total price for this line item.
|
||||||
|
double get lineTotal => quantity * unitPrice;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
/// The fulfilment status of a customer order.
|
||||||
|
enum OrderStatus {
|
||||||
|
/// Order has been placed but not yet processed.
|
||||||
|
pending,
|
||||||
|
|
||||||
|
/// Order is being prepared / packed.
|
||||||
|
processing,
|
||||||
|
|
||||||
|
/// Order has been shipped to the customer.
|
||||||
|
shipped,
|
||||||
|
|
||||||
|
/// Order has been delivered.
|
||||||
|
delivered,
|
||||||
|
|
||||||
|
/// Order was cancelled before fulfilment.
|
||||||
|
cancelled,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import 'order.dart';
|
||||||
|
|
||||||
|
/// Contract for fetching and managing customer orders.
|
||||||
|
abstract class OrdersRepository {
|
||||||
|
/// Returns all orders.
|
||||||
|
Future<List<Order>> getOrders();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../application/get_orders.dart';
|
||||||
|
import '../application/orders_controller.dart';
|
||||||
|
import '../domain/orders_repository.dart';
|
||||||
|
import 'widgets/order_card.dart';
|
||||||
|
import 'widgets/order_detail_panel.dart';
|
||||||
|
|
||||||
|
/// The main Orders page.
|
||||||
|
///
|
||||||
|
/// Displays a list of orders on the left and a detail panel on the right.
|
||||||
|
/// Users can select an order to view its full details.
|
||||||
|
class OrdersPage extends StatefulWidget {
|
||||||
|
final OrdersRepository repository;
|
||||||
|
|
||||||
|
const OrdersPage({super.key, required this.repository});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<OrdersPage> createState() => _OrdersPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OrdersPageState extends State<OrdersPage> {
|
||||||
|
late final OrdersController controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
controller = OrdersController(GetOrders(widget.repository));
|
||||||
|
controller.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: controller,
|
||||||
|
builder: (context, _) {
|
||||||
|
if (controller.isLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controller.error != null) {
|
||||||
|
return const Center(child: Text('Failed to load orders.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
// On narrow screens show only the list; on wide screens show
|
||||||
|
// a master-detail layout.
|
||||||
|
if (constraints.maxWidth < 800) {
|
||||||
|
return _buildOrderList();
|
||||||
|
}
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(width: 380, child: _buildOrderList()),
|
||||||
|
const SizedBox(width: KcSpacing.md),
|
||||||
|
Expanded(child: _buildDetail()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildOrderList() {
|
||||||
|
return ListView.separated(
|
||||||
|
itemCount: controller.orders.length,
|
||||||
|
separatorBuilder: (_, _) => const SizedBox(height: KcSpacing.sm),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final order = controller.orders[index];
|
||||||
|
return SizedBox(
|
||||||
|
height: 160,
|
||||||
|
child: OrderCard(
|
||||||
|
order: order,
|
||||||
|
isSelected: order.id == controller.selectedOrder?.id,
|
||||||
|
onTap: () => controller.selectOrder(order),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDetail() {
|
||||||
|
final selected = controller.selectedOrder;
|
||||||
|
if (selected == null) {
|
||||||
|
return const Center(child: Text('Select an order to view details'));
|
||||||
|
}
|
||||||
|
return OrderDetailPanel(order: selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../domain/order.dart';
|
||||||
|
import 'order_status_chip.dart';
|
||||||
|
|
||||||
|
/// A card displaying a summary of an [Order].
|
||||||
|
///
|
||||||
|
/// Shows the order ID, customer name, date, total, item count, and status.
|
||||||
|
/// Highlights when [isSelected] is true.
|
||||||
|
class OrderCard extends StatelessWidget {
|
||||||
|
final Order order;
|
||||||
|
final bool isSelected;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
|
const OrderCard({super.key, required this.order, this.isSelected = false, this.onTap});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
padding: const EdgeInsets.all(KcSpacing.md),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: KcColors.surface,
|
||||||
|
border: Border.all(
|
||||||
|
color: isSelected ? KcColors.denimBlue : KcColors.border,
|
||||||
|
width: isSelected ? 2 : 1,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(blurRadius: 8, offset: Offset(0, 2), color: Color(0x11000000)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
order.id,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
OrderStatusChip(status: order.status),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: KcSpacing.xs),
|
||||||
|
Text(order.customerName, style: Theme.of(context).textTheme.bodyMedium),
|
||||||
|
const SizedBox(height: KcSpacing.sm),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'\$${order.total.toStringAsFixed(2)}',
|
||||||
|
style: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
const SizedBox(width: KcSpacing.sm),
|
||||||
|
Text(
|
||||||
|
'${order.itemCount} item${order.itemCount == 1 ? '' : 's'}',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: KcColors.neutral),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Text(
|
||||||
|
_formatDate(order.orderDate),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: KcColors.neutral),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _formatDate(DateTime date) {
|
||||||
|
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../domain/order.dart';
|
||||||
|
import 'order_status_chip.dart';
|
||||||
|
|
||||||
|
/// A detail panel that shows the full information for the selected [Order].
|
||||||
|
///
|
||||||
|
/// Includes customer info, shipping address, line items table, and order total.
|
||||||
|
class OrderDetailPanel extends StatelessWidget {
|
||||||
|
final Order order;
|
||||||
|
|
||||||
|
const OrderDetailPanel({super.key, required this.order});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return KcCard(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// ── Header ─────────────────────────────────────────────────
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(child: Text('Order ${order.id}', style: theme.textTheme.headlineMedium)),
|
||||||
|
const SizedBox(width: KcSpacing.sm),
|
||||||
|
OrderStatusChip(status: order.status),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: KcSpacing.md),
|
||||||
|
|
||||||
|
// ── Customer info ──────────────────────────────────────────
|
||||||
|
Text('Customer', style: theme.textTheme.titleLarge),
|
||||||
|
const SizedBox(height: KcSpacing.sm),
|
||||||
|
_MetadataRow(label: 'Name', value: order.customerName),
|
||||||
|
_MetadataRow(label: 'Email', value: order.customerEmail),
|
||||||
|
_MetadataRow(label: 'Order Date', value: _formatDate(order.orderDate)),
|
||||||
|
const SizedBox(height: KcSpacing.md),
|
||||||
|
|
||||||
|
// ── Shipping address ───────────────────────────────────────
|
||||||
|
Text('Shipping Address', style: theme.textTheme.titleLarge),
|
||||||
|
const SizedBox(height: KcSpacing.sm),
|
||||||
|
Text(order.shippingAddress, style: theme.textTheme.bodyLarge),
|
||||||
|
const SizedBox(height: KcSpacing.md),
|
||||||
|
|
||||||
|
// ── Line items ─────────────────────────────────────────────
|
||||||
|
Text('Items', style: theme.textTheme.titleLarge),
|
||||||
|
const SizedBox(height: KcSpacing.sm),
|
||||||
|
...order.items.map(
|
||||||
|
(item) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: KcSpacing.xs),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(item.productName, style: theme.textTheme.bodyMedium),
|
||||||
|
Text(
|
||||||
|
'SKU: ${item.sku}',
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(color: KcColors.neutral),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 40,
|
||||||
|
child: Text(
|
||||||
|
'x${item.quantity}',
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 80,
|
||||||
|
child: Text(
|
||||||
|
'\$${item.lineTotal.toStringAsFixed(2)}',
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: KcSpacing.lg),
|
||||||
|
|
||||||
|
// ── Total ──────────────────────────────────────────────────
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Text('Total: ', style: theme.textTheme.titleLarge),
|
||||||
|
Text(
|
||||||
|
'\$${order.total.toStringAsFixed(2)}',
|
||||||
|
style: theme.textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _formatDate(DateTime date) {
|
||||||
|
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MetadataRow extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
const _MetadataRow({required this.label, required this.value});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: KcSpacing.xs),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 120,
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: KcColors.neutral,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(child: Text(value, style: Theme.of(context).textTheme.bodyMedium)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../domain/order_status.dart';
|
||||||
|
|
||||||
|
/// A chip that displays the [OrderStatus] of an order using the
|
||||||
|
/// design-system [KcStatusChip].
|
||||||
|
class OrderStatusChip extends StatelessWidget {
|
||||||
|
final OrderStatus status;
|
||||||
|
|
||||||
|
const OrderStatusChip({super.key, required this.status});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final (label, bg, fg) = _style(status);
|
||||||
|
return KcStatusChip(label: label, background: bg, foreground: fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static (String, Color, Color) _style(OrderStatus status) {
|
||||||
|
switch (status) {
|
||||||
|
case OrderStatus.pending:
|
||||||
|
return ('Pending', const Color(0xFFFFF8E1), KcColors.warning);
|
||||||
|
case OrderStatus.processing:
|
||||||
|
return ('Processing', const Color(0xFFE3F2FD), KcColors.denimBlue);
|
||||||
|
case OrderStatus.shipped:
|
||||||
|
return ('Shipped', const Color(0xFFE0F7FA), KcColors.deepTeal);
|
||||||
|
case OrderStatus.delivered:
|
||||||
|
return ('Delivered', const Color(0xFFE8F5E9), KcColors.success);
|
||||||
|
case OrderStatus.cancelled:
|
||||||
|
return ('Cancelled', const Color(0xFFFFEBEE), KcColors.danger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,212 @@
|
||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.13.1"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
clock:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: clock
|
||||||
|
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
design_system:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../design_system"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
|
fake_async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fake_async
|
||||||
|
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.3"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_lints
|
||||||
|
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
|
flutter_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
leak_tracker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker
|
||||||
|
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "11.0.2"
|
||||||
|
leak_tracker_flutter_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_flutter_testing
|
||||||
|
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.10"
|
||||||
|
leak_tracker_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_testing
|
||||||
|
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
lints:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.0"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.19"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.13.0"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.17.0"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.2"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.10"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.0.2"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.11.4 <4.0.0"
|
||||||
|
flutter: ">=3.18.0-18.0.pre.54"
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
name: feature_orders
|
||||||
|
description: "Order management feature for Kell Creations."
|
||||||
|
version: 0.0.1
|
||||||
|
publish_to: "none"
|
||||||
|
homepage:
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ^3.11.4
|
||||||
|
flutter: ">=1.17.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
design_system:
|
||||||
|
path: ../design_system
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
flutter_lints: ^6.0.0
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:feature_orders/feature_orders.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late FakeOrdersRepository repository;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
repository = FakeOrdersRepository();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('FakeOrdersRepository', () {
|
||||||
|
test('getOrders returns six sample orders', () async {
|
||||||
|
final orders = await repository.getOrders();
|
||||||
|
expect(orders.length, 6);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getOrders returns orders with expected IDs', () async {
|
||||||
|
final orders = await repository.getOrders();
|
||||||
|
final ids = orders.map((o) => o.id).toList();
|
||||||
|
expect(ids, contains('KC-1001'));
|
||||||
|
expect(ids, contains('KC-1002'));
|
||||||
|
expect(ids, contains('KC-1003'));
|
||||||
|
expect(ids, contains('KC-1004'));
|
||||||
|
expect(ids, contains('KC-1005'));
|
||||||
|
expect(ids, contains('KC-1006'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getOrders returns orders with various statuses', () async {
|
||||||
|
final orders = await repository.getOrders();
|
||||||
|
final statuses = orders.map((o) => o.status).toSet();
|
||||||
|
expect(statuses, contains(OrderStatus.pending));
|
||||||
|
expect(statuses, contains(OrderStatus.processing));
|
||||||
|
expect(statuses, contains(OrderStatus.shipped));
|
||||||
|
expect(statuses, contains(OrderStatus.delivered));
|
||||||
|
expect(statuses, contains(OrderStatus.cancelled));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('order totals are computed correctly', () async {
|
||||||
|
final orders = await repository.getOrders();
|
||||||
|
// KC-1001: 2 * 12.99 + 1 * 16.50 = 42.48
|
||||||
|
final order1001 = orders.firstWhere((o) => o.id == 'KC-1001');
|
||||||
|
expect(order1001.total, closeTo(42.48, 0.01));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('order item count is computed correctly', () async {
|
||||||
|
final orders = await repository.getOrders();
|
||||||
|
// KC-1001: 2 + 1 = 3 items
|
||||||
|
final order1001 = orders.firstWhere((o) => o.id == 'KC-1001');
|
||||||
|
expect(order1001.itemCount, 3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
// This file ensures the barrel export compiles correctly.
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:feature_orders/feature_orders.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('barrel export exposes Order', () {
|
||||||
|
// Verify the domain model is accessible through the barrel export.
|
||||||
|
final order = Order(
|
||||||
|
id: 'test',
|
||||||
|
customerName: 'Test',
|
||||||
|
customerEmail: 'test@test.com',
|
||||||
|
orderDate: DateTime(2026, 1, 1),
|
||||||
|
status: OrderStatus.pending,
|
||||||
|
items: const [],
|
||||||
|
shippingAddress: '123 Test St',
|
||||||
|
);
|
||||||
|
expect(order.id, 'test');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:feature_orders/feature_orders.dart';
|
||||||
|
import 'package:feature_orders/src/application/get_orders.dart';
|
||||||
|
import 'package:feature_orders/src/application/orders_controller.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late FakeOrdersRepository repository;
|
||||||
|
late OrdersController controller;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
repository = FakeOrdersRepository();
|
||||||
|
controller = OrdersController(GetOrders(repository));
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() {
|
||||||
|
controller.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('OrdersController', () {
|
||||||
|
test('starts with empty state', () {
|
||||||
|
expect(controller.isLoading, false);
|
||||||
|
expect(controller.orders, isEmpty);
|
||||||
|
expect(controller.selectedOrder, isNull);
|
||||||
|
expect(controller.error, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('load populates orders and auto-selects first', () async {
|
||||||
|
await controller.load();
|
||||||
|
|
||||||
|
expect(controller.isLoading, false);
|
||||||
|
expect(controller.orders.length, 6);
|
||||||
|
expect(controller.selectedOrder, isNotNull);
|
||||||
|
expect(controller.selectedOrder!.id, 'KC-1001');
|
||||||
|
expect(controller.error, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('selectOrder updates selectedOrder', () async {
|
||||||
|
await controller.load();
|
||||||
|
|
||||||
|
final third = controller.orders[2];
|
||||||
|
controller.selectOrder(third);
|
||||||
|
|
||||||
|
expect(controller.selectedOrder!.id, third.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:feature_orders/feature_orders.dart';
|
||||||
|
import 'package:feature_orders/src/presentation/widgets/order_card.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final sampleOrder = Order(
|
||||||
|
id: 'KC-9999',
|
||||||
|
customerName: 'Test Customer',
|
||||||
|
customerEmail: 'test@example.com',
|
||||||
|
orderDate: DateTime(2026, 4, 1),
|
||||||
|
status: OrderStatus.processing,
|
||||||
|
shippingAddress: '123 Test St, Test City, TS 00000',
|
||||||
|
items: const [
|
||||||
|
OrderItem(productName: 'Test Product', sku: 'TP-001', quantity: 2, unitPrice: 10.00),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget buildTestWidget({bool isSelected = false, VoidCallback? onTap}) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: buildKcTheme(),
|
||||||
|
home: Scaffold(
|
||||||
|
body: SizedBox(
|
||||||
|
height: 200,
|
||||||
|
width: 400,
|
||||||
|
child: OrderCard(order: sampleOrder, isSelected: isSelected, onTap: onTap),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
group('OrderCard', () {
|
||||||
|
testWidgets('displays order ID', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget());
|
||||||
|
expect(find.text('KC-9999'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('displays customer name', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget());
|
||||||
|
expect(find.text('Test Customer'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('displays total', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget());
|
||||||
|
expect(find.text('\$20.00'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('displays item count', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget());
|
||||||
|
expect(find.text('2 items'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('displays status chip', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget());
|
||||||
|
expect(find.text('Processing'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('displays date', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget());
|
||||||
|
expect(find.text('2026-04-01'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('calls onTap when tapped', (tester) async {
|
||||||
|
var tapped = false;
|
||||||
|
await tester.pumpWidget(buildTestWidget(onTap: () => tapped = true));
|
||||||
|
await tester.tap(find.text('KC-9999'));
|
||||||
|
expect(tapped, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:feature_orders/feature_orders.dart';
|
||||||
|
import 'package:feature_orders/src/presentation/widgets/order_detail_panel.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final sampleOrder = Order(
|
||||||
|
id: 'KC-9999',
|
||||||
|
customerName: 'Test Customer',
|
||||||
|
customerEmail: 'test@example.com',
|
||||||
|
orderDate: DateTime(2026, 4, 1),
|
||||||
|
status: OrderStatus.shipped,
|
||||||
|
shippingAddress: '123 Test St, Test City, TS 00000',
|
||||||
|
items: const [
|
||||||
|
OrderItem(productName: 'Test Product', sku: 'TP-001', quantity: 2, unitPrice: 10.00),
|
||||||
|
OrderItem(productName: 'Another Product', sku: 'AP-002', quantity: 1, unitPrice: 15.50),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget buildTestWidget(Order order) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: buildKcTheme(),
|
||||||
|
home: Scaffold(
|
||||||
|
body: SingleChildScrollView(child: OrderDetailPanel(order: order)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
group('OrderDetailPanel', () {
|
||||||
|
testWidgets('displays order ID in header', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget(sampleOrder));
|
||||||
|
expect(find.text('Order KC-9999'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('displays status chip', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget(sampleOrder));
|
||||||
|
expect(find.text('Shipped'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('displays customer name', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget(sampleOrder));
|
||||||
|
expect(find.text('Test Customer'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('displays customer email', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget(sampleOrder));
|
||||||
|
expect(find.text('test@example.com'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('displays shipping address', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget(sampleOrder));
|
||||||
|
expect(find.text('123 Test St, Test City, TS 00000'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('displays line item product names', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget(sampleOrder));
|
||||||
|
expect(find.text('Test Product'), findsOneWidget);
|
||||||
|
expect(find.text('Another Product'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('displays line item SKUs', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget(sampleOrder));
|
||||||
|
expect(find.text('SKU: TP-001'), findsOneWidget);
|
||||||
|
expect(find.text('SKU: AP-002'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('displays order total', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget(sampleOrder));
|
||||||
|
// 2 * 10.00 + 1 * 15.50 = 35.50
|
||||||
|
expect(find.text('\$35.50'), findsOneWidget);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:feature_orders/feature_orders.dart';
|
||||||
|
import 'package:feature_orders/src/presentation/widgets/order_status_chip.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
Widget buildTestWidget(OrderStatus status) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: buildKcTheme(),
|
||||||
|
home: Scaffold(body: OrderStatusChip(status: status)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
group('OrderStatusChip', () {
|
||||||
|
testWidgets('shows Pending label for pending status', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget(OrderStatus.pending));
|
||||||
|
expect(find.text('Pending'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('shows Processing label for processing status', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget(OrderStatus.processing));
|
||||||
|
expect(find.text('Processing'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('shows Shipped label for shipped status', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget(OrderStatus.shipped));
|
||||||
|
expect(find.text('Shipped'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('shows Delivered label for delivered status', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget(OrderStatus.delivered));
|
||||||
|
expect(find.text('Delivered'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('shows Cancelled label for cancelled status', (tester) async {
|
||||||
|
await tester.pumpWidget(buildTestWidget(OrderStatus.cancelled));
|
||||||
|
expect(find.text('Cancelled'), findsOneWidget);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue