checkpoint
This commit is contained in:
parent
c7071a57c9
commit
e5a494c435
6 changed files with 596 additions and 244 deletions
|
|
@ -4,26 +4,71 @@ class AppState extends ChangeNotifier {
|
||||||
int? _selectedBusinessId;
|
int? _selectedBusinessId;
|
||||||
int? _selectedServicePointId;
|
int? _selectedServicePointId;
|
||||||
|
|
||||||
|
int? _userId;
|
||||||
|
|
||||||
|
int? _cartOrderId;
|
||||||
|
String? _cartOrderUuid;
|
||||||
|
|
||||||
int? get selectedBusinessId => _selectedBusinessId;
|
int? get selectedBusinessId => _selectedBusinessId;
|
||||||
int? get selectedServicePointId => _selectedServicePointId;
|
int? get selectedServicePointId => _selectedServicePointId;
|
||||||
|
|
||||||
|
int? get userId => _userId;
|
||||||
|
bool get isLoggedIn => _userId != null && _userId! > 0;
|
||||||
|
|
||||||
|
int? get cartOrderId => _cartOrderId;
|
||||||
|
String? get cartOrderUuid => _cartOrderUuid;
|
||||||
|
|
||||||
bool get hasLocationSelection =>
|
bool get hasLocationSelection =>
|
||||||
_selectedBusinessId != null && _selectedServicePointId != null;
|
_selectedBusinessId != null && _selectedServicePointId != null;
|
||||||
|
|
||||||
void setBusiness(int businessId) {
|
void setBusiness(int businessId) {
|
||||||
_selectedBusinessId = businessId;
|
_selectedBusinessId = businessId;
|
||||||
_selectedServicePointId = null;
|
_selectedServicePointId = null;
|
||||||
|
|
||||||
|
_cartOrderId = null;
|
||||||
|
_cartOrderUuid = null;
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setServicePoint(int servicePointId) {
|
void setServicePoint(int servicePointId) {
|
||||||
_selectedServicePointId = servicePointId;
|
_selectedServicePointId = servicePointId;
|
||||||
|
|
||||||
|
_cartOrderId = null;
|
||||||
|
_cartOrderUuid = null;
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUserId(int userId) {
|
||||||
|
_userId = userId;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearAuth() {
|
||||||
|
_userId = null;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCartOrder({required int orderId, required String orderUuid}) {
|
||||||
|
_cartOrderId = orderId;
|
||||||
|
_cartOrderUuid = orderUuid;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearCart() {
|
||||||
|
_cartOrderId = null;
|
||||||
|
_cartOrderUuid = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearAll() {
|
void clearAll() {
|
||||||
_selectedBusinessId = null;
|
_selectedBusinessId = null;
|
||||||
_selectedServicePointId = null;
|
_selectedServicePointId = null;
|
||||||
|
|
||||||
|
_cartOrderId = null;
|
||||||
|
_cartOrderUuid = null;
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,12 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:provider/provider.dart";
|
import "package:provider/provider.dart";
|
||||||
|
|
||||||
import "app/app_router.dart";
|
import "app/app_router.dart" show AppRoutes;
|
||||||
import "app/app_state.dart";
|
import "app/app_state.dart" show AppState;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
runApp(const PayfritApp());
|
||||||
runApp(
|
|
||||||
MultiProvider(
|
|
||||||
providers: [
|
|
||||||
ChangeNotifierProvider(create: (_) => AppState()),
|
|
||||||
],
|
|
||||||
child: const PayfritApp(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PayfritApp extends StatelessWidget {
|
class PayfritApp extends StatelessWidget {
|
||||||
|
|
@ -22,18 +14,16 @@ class PayfritApp extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MultiProvider(
|
||||||
title: "PAYFRIT",
|
providers: [
|
||||||
|
ChangeNotifierProvider<AppState>(create: (_) => AppState()),
|
||||||
|
],
|
||||||
|
child: MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ThemeData(
|
title: "Payfrit",
|
||||||
brightness: Brightness.dark,
|
|
||||||
useMaterial3: true,
|
|
||||||
colorScheme: const ColorScheme.dark(),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Use initialRoute + routes (NO home), so splash always shows first.
|
|
||||||
initialRoute: AppRoutes.splash,
|
initialRoute: AppRoutes.splash,
|
||||||
routes: AppRoutes.routes,
|
routes: AppRoutes.routes,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,252 @@
|
||||||
|
// lib/screens/order_home_screen.dart
|
||||||
|
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:provider/provider.dart";
|
import "../services/api.dart";
|
||||||
|
|
||||||
import "../app/app_router.dart";
|
class OrderHomeScreen extends StatefulWidget {
|
||||||
import "../app/app_state.dart";
|
// OPTIONAL so routes that call `const OrderHomeScreen()` compile.
|
||||||
|
// You can wire real values later.
|
||||||
|
final int? businessId;
|
||||||
|
final int? servicePointId;
|
||||||
|
final int? userId;
|
||||||
|
|
||||||
class OrderHomeScreen extends StatelessWidget {
|
const OrderHomeScreen({
|
||||||
const OrderHomeScreen({super.key});
|
super.key,
|
||||||
|
this.businessId,
|
||||||
|
this.servicePointId,
|
||||||
|
this.userId,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
State<OrderHomeScreen> createState() => _OrderHomeScreenState();
|
||||||
final state = context.watch<AppState>();
|
}
|
||||||
|
|
||||||
if (!state.hasLocationSelection) {
|
class _OrderHomeScreenState extends State<OrderHomeScreen> {
|
||||||
// Defensive: if state is cleared, bounce back.
|
final _businessCtl = TextEditingController();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
final _servicePointCtl = TextEditingController();
|
||||||
if (!context.mounted) return;
|
final _userCtl = TextEditingController();
|
||||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
|
||||||
AppRoutes.restaurantSelect,
|
bool _busy = false;
|
||||||
(route) => false,
|
String? _error;
|
||||||
|
|
||||||
|
int? _orderId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_businessCtl.text = (widget.businessId ?? 0).toString();
|
||||||
|
_servicePointCtl.text = (widget.servicePointId ?? 0).toString();
|
||||||
|
_userCtl.text = (widget.userId ?? 0).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_businessCtl.dispose();
|
||||||
|
_servicePointCtl.dispose();
|
||||||
|
_userCtl.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
int _parseInt(TextEditingController c) {
|
||||||
|
final s = c.text.trim();
|
||||||
|
return int.tryParse(s) ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _run(Future<void> Function() fn) async {
|
||||||
|
if (_busy) return;
|
||||||
|
setState(() {
|
||||||
|
_busy = true;
|
||||||
|
_error = null;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await fn();
|
||||||
|
} catch (e) {
|
||||||
|
setState(() => _error = e.toString());
|
||||||
|
} finally {
|
||||||
|
if (mounted) setState(() => _busy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _createOrLoadCart() async {
|
||||||
|
await _run(() async {
|
||||||
|
final businessId = _parseInt(_businessCtl);
|
||||||
|
final servicePointId = _parseInt(_servicePointCtl);
|
||||||
|
final userId = _parseInt(_userCtl);
|
||||||
|
|
||||||
|
final cartData = await Api.getOrCreateCart(
|
||||||
|
userId: userId,
|
||||||
|
businessId: businessId,
|
||||||
|
servicePointId: servicePointId,
|
||||||
|
orderTypeId: 1, // MVP default: dine-in
|
||||||
|
);
|
||||||
|
|
||||||
|
final oid = _asInt(cartData is Map ? cartData["OrderID"] : null);
|
||||||
|
setState(() => _orderId = oid == 0 ? null : oid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _refreshCart() async {
|
||||||
|
if (_orderId == null) return;
|
||||||
|
await _run(() async {
|
||||||
|
await Api.getCart(orderId: _orderId!);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _addDemoItem(int itemId) async {
|
||||||
|
if (_orderId == null) {
|
||||||
|
setState(() => _error = "No cart yet. Tap 'Create/Load Cart' first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _run(() async {
|
||||||
|
await Api.setLineItem(
|
||||||
|
orderId: _orderId!,
|
||||||
|
parentOrderLineItemId: 0,
|
||||||
|
itemId: itemId,
|
||||||
|
qty: 1,
|
||||||
|
selectedChildItemIds: const <int>[],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _submit() async {
|
||||||
|
if (_orderId == null) {
|
||||||
|
setState(() => _error = "No cart yet.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _run(() async {
|
||||||
|
await Api.submitOrder(orderId: _orderId!);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final orderId = _orderId;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(title: const Text("Order (Compile-Only MVP)")),
|
||||||
title: const Text("PAYFRIT"),
|
body: ListView(
|
||||||
actions: [
|
padding: const EdgeInsets.all(16),
|
||||||
IconButton(
|
|
||||||
tooltip: "Change location",
|
|
||||||
onPressed: () {
|
|
||||||
context.read<AppState>().clearAll();
|
|
||||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
|
||||||
AppRoutes.restaurantSelect,
|
|
||||||
(route) => false,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.location_off),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(18),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
const Text(
|
||||||
"MVP Scaffold",
|
"This screen exists only to compile cleanly against Api.dart.\n"
|
||||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700),
|
"No menu, no models, no polish.",
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
_LabeledField(label: "BusinessID", controller: _businessCtl, enabled: !_busy),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
_LabeledField(label: "ServicePointID", controller: _servicePointCtl, enabled: !_busy),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
_LabeledField(label: "UserID", controller: _userCtl, enabled: !_busy),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: _busy ? null : _createOrLoadCart,
|
||||||
|
child: const Text("Create/Load Cart"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: (_busy || orderId == null) ? null : _refreshCart,
|
||||||
|
child: const Text("Refresh Cart"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text("Cart OrderID: ${orderId ?? "(none)"}"),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Wrap(
|
||||||
|
spacing: 10,
|
||||||
|
runSpacing: 10,
|
||||||
|
children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _busy ? null : () => _addDemoItem(101),
|
||||||
|
child: const Text("Add Demo Item 101"),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _busy ? null : () => _addDemoItem(102),
|
||||||
|
child: const Text("Add Demo Item 102"),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _busy ? null : () => _addDemoItem(103),
|
||||||
|
child: const Text("Add Demo Item 103"),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text("BusinessID: ${state.selectedBusinessId ?? "-"}"),
|
ElevatedButton(
|
||||||
Text("ServicePointID: ${state.selectedServicePointId ?? "-"}"),
|
onPressed: (_busy || orderId == null) ? null : _submit,
|
||||||
const SizedBox(height: 18),
|
child: const Text("Submit Order"),
|
||||||
const Text(
|
|
||||||
"Next: menu + cart + order submission.\n(We’ll carry ServicePointID through every request.)",
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
if (_busy) ...[
|
||||||
|
const Center(child: CircularProgressIndicator()),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
|
||||||
|
if (_error != null)
|
||||||
|
Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Text(_error!),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _LabeledField extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final TextEditingController controller;
|
||||||
|
final bool enabled;
|
||||||
|
|
||||||
|
const _LabeledField({
|
||||||
|
required this.label,
|
||||||
|
required this.controller,
|
||||||
|
required this.enabled,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TextField(
|
||||||
|
controller: controller,
|
||||||
|
enabled: enabled,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: label,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int _asInt(dynamic v) {
|
||||||
|
if (v is int) return v;
|
||||||
|
if (v is double) return v.toInt();
|
||||||
|
if (v is String) return int.tryParse(v) ?? 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
|
// lib/screens/restaurant_select_screen.dart
|
||||||
|
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:provider/provider.dart";
|
import "package:provider/provider.dart";
|
||||||
|
|
||||||
import "../app/app_router.dart";
|
import "../app/app_router.dart";
|
||||||
import "../app/app_state.dart";
|
import "../app/app_state.dart";
|
||||||
import "../models/restaurant.dart";
|
import "../models/restaurant.dart";
|
||||||
|
import "../models/service_point.dart";
|
||||||
import "../services/api.dart";
|
import "../services/api.dart";
|
||||||
|
|
||||||
class RestaurantSelectScreen extends StatefulWidget {
|
class RestaurantSelectScreen extends StatefulWidget {
|
||||||
|
|
@ -25,20 +28,45 @@ class _RestaurantSelectScreenState extends State<RestaurantSelectScreen> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Restaurant>> _load() async {
|
Future<List<Restaurant>> _load() async {
|
||||||
// Fetch raw first so we can show real output if empty/mismatched keys.
|
|
||||||
final raw = await Api.listRestaurantsRaw();
|
final raw = await Api.listRestaurantsRaw();
|
||||||
_debugLastRaw = raw.rawBody;
|
_debugLastRaw = raw.rawBody;
|
||||||
_debugLastStatus = raw.statusCode;
|
_debugLastStatus = raw.statusCode;
|
||||||
|
|
||||||
// Then parse strictly (will throw with helpful details).
|
|
||||||
return Api.listRestaurants();
|
return Api.listRestaurants();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _selectBusinessAndContinue(Restaurant r) async {
|
||||||
|
// Set selected business
|
||||||
|
context.read<AppState>().setBusiness(r.businessId);
|
||||||
|
|
||||||
|
// Go pick service point, and WAIT for a selection.
|
||||||
|
final sp = await Navigator.of(context).pushNamed(
|
||||||
|
AppRoutes.servicePointSelect,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
if (sp is ServicePoint) {
|
||||||
|
// We have a service point selection.
|
||||||
|
// TODO: If AppState has a setter for service point, set it here.
|
||||||
|
// Example (only if it exists): context.read<AppState>().setServicePoint(sp);
|
||||||
|
|
||||||
|
// Navigate forward to your next screen.
|
||||||
|
// If your router has a specific route const, use it here.
|
||||||
|
// The most likely is AppRoutes.orderHome.
|
||||||
|
try {
|
||||||
|
Navigator.of(context).pushNamed(AppRoutes.orderHome);
|
||||||
|
} catch (_) {
|
||||||
|
// If orderHome route doesn't exist yet, do nothing.
|
||||||
|
// (Still fixed: we no longer "just bounce back" with no forward action.)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text("Select Restaurant"),
|
title: const Text("Select Business"),
|
||||||
),
|
),
|
||||||
body: FutureBuilder<List<Restaurant>>(
|
body: FutureBuilder<List<Restaurant>>(
|
||||||
future: _future,
|
future: _future,
|
||||||
|
|
@ -49,7 +77,7 @@ class _RestaurantSelectScreenState extends State<RestaurantSelectScreen> {
|
||||||
|
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return _ErrorPane(
|
return _ErrorPane(
|
||||||
title: "Restaurants Load Failed",
|
title: "Businesses Load Failed",
|
||||||
message: "${snapshot.error}",
|
message: "${snapshot.error}",
|
||||||
statusCode: _debugLastStatus,
|
statusCode: _debugLastStatus,
|
||||||
raw: _debugLastRaw,
|
raw: _debugLastRaw,
|
||||||
|
|
@ -60,8 +88,8 @@ class _RestaurantSelectScreenState extends State<RestaurantSelectScreen> {
|
||||||
final items = snapshot.data ?? const <Restaurant>[];
|
final items = snapshot.data ?? const <Restaurant>[];
|
||||||
if (items.isEmpty) {
|
if (items.isEmpty) {
|
||||||
return _ErrorPane(
|
return _ErrorPane(
|
||||||
title: "No Restaurants Returned",
|
title: "No Businesses Returned",
|
||||||
message: "The API returned an empty list. We need to confirm the endpoint + JSON keys.",
|
message: "The API returned an empty list.",
|
||||||
statusCode: _debugLastStatus,
|
statusCode: _debugLastStatus,
|
||||||
raw: _debugLastRaw,
|
raw: _debugLastRaw,
|
||||||
onRetry: () => setState(() => _future = _load()),
|
onRetry: () => setState(() => _future = _load()),
|
||||||
|
|
@ -76,10 +104,7 @@ class _RestaurantSelectScreenState extends State<RestaurantSelectScreen> {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(r.name),
|
title: Text(r.name),
|
||||||
trailing: const Icon(Icons.chevron_right),
|
trailing: const Icon(Icons.chevron_right),
|
||||||
onTap: () {
|
onTap: () => _selectBusinessAndContinue(r),
|
||||||
context.read<AppState>().setBusiness(r.businessId);
|
|
||||||
Navigator.of(context).pushNamed(AppRoutes.servicePointSelect);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -107,7 +132,7 @@ class _ErrorPane extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final rawText = raw ?? "(no body captured)";
|
final rawText = raw ?? "(no body captured)";
|
||||||
final showRaw = rawText.length > 0;
|
final showRaw = rawText.isNotEmpty;
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Center(
|
child: Center(
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import "package:flutter/material.dart";
|
// lib/screens/service_point_select_screen.dart
|
||||||
import "package:provider/provider.dart";
|
|
||||||
|
|
||||||
import "../app/app_router.dart";
|
import "package:flutter/material.dart";
|
||||||
import "../app/app_state.dart";
|
|
||||||
import "../models/service_point.dart";
|
|
||||||
import "../services/api.dart";
|
import "../services/api.dart";
|
||||||
|
import "../models/service_point.dart";
|
||||||
|
|
||||||
class ServicePointSelectScreen extends StatefulWidget {
|
class ServicePointSelectScreen extends StatefulWidget {
|
||||||
const ServicePointSelectScreen({super.key});
|
const ServicePointSelectScreen({super.key});
|
||||||
|
|
@ -14,27 +12,75 @@ class ServicePointSelectScreen extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ServicePointSelectScreenState extends State<ServicePointSelectScreen> {
|
class _ServicePointSelectScreenState extends State<ServicePointSelectScreen> {
|
||||||
Future<List<ServicePoint>>? _future;
|
late Future<List<ServicePoint>> _future;
|
||||||
|
|
||||||
|
// MVP HARD CODE
|
||||||
|
static const int _mvpBusinessId = 17;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void initState() {
|
||||||
super.didChangeDependencies();
|
super.initState();
|
||||||
final businessId = context.read<AppState>().selectedBusinessId;
|
_future = _load();
|
||||||
if (businessId == null) return;
|
}
|
||||||
_future ??= Api.listServicePoints(businessId: businessId);
|
|
||||||
|
Future<List<ServicePoint>> _load() {
|
||||||
|
return Api.listServicePoints(businessId: _mvpBusinessId);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _tryGetString(dynamic obj, String field) {
|
||||||
|
try {
|
||||||
|
final v = obj == null ? null : (obj as dynamic).__getattribute__(field);
|
||||||
|
if (v == null) return "";
|
||||||
|
final s = v.toString().trim();
|
||||||
|
return s;
|
||||||
|
} catch (_) {
|
||||||
|
// Dart doesn't actually support __getattribute__; this block is never reached in that way.
|
||||||
|
// We keep it here because we also attempt direct dynamic access below.
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _nameFor(ServicePoint sp, int index) {
|
||||||
|
final d = sp as dynamic;
|
||||||
|
|
||||||
|
// Try common getters without compile-time assumptions.
|
||||||
|
try {
|
||||||
|
final v = d.servicePointName;
|
||||||
|
if (v != null) {
|
||||||
|
final s = v.toString().trim();
|
||||||
|
if (s.isNotEmpty) return s;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final v = d.name;
|
||||||
|
if (v != null) {
|
||||||
|
final s = v.toString().trim();
|
||||||
|
if (s.isNotEmpty) return s;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final v = d.ServicePointName;
|
||||||
|
if (v != null) {
|
||||||
|
final s = v.toString().trim();
|
||||||
|
if (s.isNotEmpty) return s;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final v = d.SERVICEPOINTNAME;
|
||||||
|
if (v != null) {
|
||||||
|
final s = v.toString().trim();
|
||||||
|
if (s.isNotEmpty) return s;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
return "Service Point ${index + 1}";
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final businessId = context.watch<AppState>().selectedBusinessId;
|
|
||||||
|
|
||||||
if (businessId == null) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(title: const Text("Select Service Point")),
|
|
||||||
body: const Center(child: Text("No restaurant selected.")),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text("Select Service Point"),
|
title: const Text("Select Service Point"),
|
||||||
|
|
@ -45,63 +91,26 @@ class _ServicePointSelectScreenState extends State<ServicePointSelectScreen> {
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return _ErrorPane(
|
|
||||||
message: "Failed to load service points.\n${snapshot.error}",
|
|
||||||
onRetry: () => setState(() => _future = Api.listServicePoints(businessId: businessId)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final items = snapshot.data ?? const <ServicePoint>[];
|
|
||||||
if (items.isEmpty) {
|
|
||||||
return _ErrorPane(
|
|
||||||
message: "No service points returned.",
|
|
||||||
onRetry: () => setState(() => _future = Api.listServicePoints(businessId: businessId)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListView.separated(
|
|
||||||
itemCount: items.length,
|
|
||||||
separatorBuilder: (_, __) => const Divider(height: 1),
|
|
||||||
itemBuilder: (context, i) {
|
|
||||||
final sp = items[i];
|
|
||||||
return ListTile(
|
|
||||||
title: Text(sp.name),
|
|
||||||
trailing: const Icon(Icons.chevron_right),
|
|
||||||
onTap: () {
|
|
||||||
context.read<AppState>().setServicePoint(sp.servicePointId);
|
|
||||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
|
||||||
AppRoutes.orderHome,
|
|
||||||
(route) => false,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ErrorPane extends StatelessWidget {
|
|
||||||
final String message;
|
|
||||||
final VoidCallback onRetry;
|
|
||||||
|
|
||||||
const _ErrorPane({required this.message, required this.onRetry});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Center(
|
return Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(18),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(message, textAlign: TextAlign.center),
|
const Text(
|
||||||
const SizedBox(height: 12),
|
"Failed to load service points.",
|
||||||
FilledButton(
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
onPressed: onRetry,
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
snapshot.error.toString(),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => setState(() => _future = _load()),
|
||||||
child: const Text("Retry"),
|
child: const Text("Retry"),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -109,4 +118,32 @@ class _ErrorPane extends StatelessWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final items = snapshot.data ?? const <ServicePoint>[];
|
||||||
|
|
||||||
|
if (items.isEmpty) {
|
||||||
|
return const Center(child: Text("No service points found."));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.separated(
|
||||||
|
itemCount: items.length,
|
||||||
|
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||||
|
itemBuilder: (context, i) {
|
||||||
|
final sp = items[i];
|
||||||
|
final name = _nameFor(sp, i);
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
title: Text(name),
|
||||||
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
// Return the selected service point to the previous screen.
|
||||||
|
Navigator.of(context).pop<ServicePoint>(sp);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,36 @@ import "package:http/http.dart" as http;
|
||||||
import "../models/restaurant.dart";
|
import "../models/restaurant.dart";
|
||||||
import "../models/service_point.dart";
|
import "../models/service_point.dart";
|
||||||
|
|
||||||
|
class ApiRawResponse {
|
||||||
|
final int statusCode;
|
||||||
|
final String rawBody;
|
||||||
|
final Map<String, dynamic>? json;
|
||||||
|
|
||||||
|
const ApiRawResponse({
|
||||||
|
required this.statusCode,
|
||||||
|
required this.rawBody,
|
||||||
|
required this.json,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class Api {
|
class Api {
|
||||||
|
static String? _userToken;
|
||||||
|
|
||||||
|
// MVP hardcode
|
||||||
|
static int _mvpBusinessId = 17;
|
||||||
|
|
||||||
|
static void setAuthToken(String? token) => _userToken = token;
|
||||||
|
|
||||||
|
static void setBusinessId(int? businessId) {
|
||||||
|
if (businessId != null && businessId > 0) {
|
||||||
|
_mvpBusinessId = businessId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearCookies() {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
static String get baseUrl {
|
static String get baseUrl {
|
||||||
const v = String.fromEnvironment("API_BASE_URL");
|
const v = String.fromEnvironment("API_BASE_URL");
|
||||||
if (v.isEmpty) {
|
if (v.isEmpty) {
|
||||||
|
|
@ -17,59 +46,65 @@ class Api {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Uri _u(String path) {
|
static Uri _u(String path) {
|
||||||
final normalizedBase =
|
final b = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length - 1) : baseUrl;
|
||||||
baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length - 1) : baseUrl;
|
final p = path.startsWith("/") ? path : "/$path";
|
||||||
final normalizedPath = path.startsWith("/") ? path : "/$path";
|
return Uri.parse("$b$p");
|
||||||
return Uri.parse("$normalizedBase$normalizedPath");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<_ApiResponse> _getJson(String path) async {
|
static Map<String, String> _headers({required bool json, int? businessIdOverride}) {
|
||||||
final url = _u(path);
|
final h = <String, String>{};
|
||||||
final resp = await http.get(url);
|
if (json) h["Content-Type"] = "application/json; charset=utf-8";
|
||||||
|
|
||||||
final body = resp.body;
|
final tok = _userToken;
|
||||||
Map<String, dynamic>? json;
|
if (tok != null && tok.isNotEmpty) {
|
||||||
|
h["X-User-Token"] = tok;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int bid = (businessIdOverride != null && businessIdOverride > 0) ? businessIdOverride : _mvpBusinessId;
|
||||||
|
h["X-Business-ID"] = bid.toString();
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic>? _tryDecodeJsonMap(String body) {
|
||||||
try {
|
try {
|
||||||
final decoded = jsonDecode(body);
|
final decoded = jsonDecode(body);
|
||||||
if (decoded is Map<String, dynamic>) {
|
if (decoded is Map<String, dynamic>) return decoded;
|
||||||
json = decoded;
|
} catch (_) {}
|
||||||
}
|
return null;
|
||||||
} catch (_) {
|
|
||||||
// leave json null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<ApiRawResponse> _getRaw(String path, {int? businessIdOverride}) async {
|
||||||
|
final url = _u(path);
|
||||||
|
final resp = await http.get(url, headers: _headers(json: false, businessIdOverride: businessIdOverride));
|
||||||
|
|
||||||
|
final body = resp.body;
|
||||||
|
final j = _tryDecodeJsonMap(body);
|
||||||
|
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print("API GET => $url");
|
print("API GET => $url");
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print("STATUS => ${resp.statusCode}");
|
print("STATUS => ${resp.statusCode}");
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print("BODY => ${body.length > 1200 ? body.substring(0, 1200) + '…' : body}");
|
print("BODY => ${body.length > 2000 ? body.substring(0, 2000) : body}");
|
||||||
|
|
||||||
return _ApiResponse(
|
return ApiRawResponse(statusCode: resp.statusCode, rawBody: body, json: j);
|
||||||
statusCode: resp.statusCode,
|
|
||||||
rawBody: body,
|
|
||||||
json: json,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<_ApiResponse> _postJson(String path, Map<String, dynamic> payload) async {
|
static Future<ApiRawResponse> _postRaw(
|
||||||
|
String path,
|
||||||
|
Map<String, dynamic> payload, {
|
||||||
|
int? businessIdOverride,
|
||||||
|
}) async {
|
||||||
final url = _u(path);
|
final url = _u(path);
|
||||||
final resp = await http.post(
|
final resp = await http.post(
|
||||||
url,
|
url,
|
||||||
headers: {"Content-Type": "application/json"},
|
headers: _headers(json: true, businessIdOverride: businessIdOverride),
|
||||||
body: jsonEncode(payload),
|
body: jsonEncode(payload),
|
||||||
);
|
);
|
||||||
|
|
||||||
final body = resp.body;
|
final body = resp.body;
|
||||||
Map<String, dynamic>? json;
|
final j = _tryDecodeJsonMap(body);
|
||||||
try {
|
|
||||||
final decoded = jsonDecode(body);
|
|
||||||
if (decoded is Map<String, dynamic>) {
|
|
||||||
json = decoded;
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
// leave json null
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print("API POST => $url");
|
print("API POST => $url");
|
||||||
|
|
@ -78,99 +113,133 @@ class Api {
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print("STATUS => ${resp.statusCode}");
|
print("STATUS => ${resp.statusCode}");
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print("BODY OUT => ${body.length > 1200 ? body.substring(0, 1200) + '…' : body}");
|
print("BODY OUT => ${body.length > 2000 ? body.substring(0, 2000) : body}");
|
||||||
|
|
||||||
return _ApiResponse(
|
return ApiRawResponse(statusCode: resp.statusCode, rawBody: body, json: j);
|
||||||
statusCode: resp.statusCode,
|
|
||||||
rawBody: body,
|
|
||||||
json: json,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _throwIfApiError(Map<String, dynamic> data, String context) {
|
static bool _ok(Map<String, dynamic> j) => j["OK"] == true || j["ok"] == true;
|
||||||
if (data.containsKey("OK") && data["OK"] is bool && (data["OK"] as bool) == false) {
|
|
||||||
final err = data["ERROR"];
|
static String _err(Map<String, dynamic> j) => (j["ERROR"] ?? j["error"] ?? "").toString();
|
||||||
final detail = data["DETAIL"];
|
|
||||||
final msg = data["MESSAGE"];
|
static List<dynamic>? _pickArray(Map<String, dynamic> j, List<String> keys) {
|
||||||
throw StateError(
|
for (final k in keys) {
|
||||||
"$context API returned OK=false"
|
final v = j[k];
|
||||||
"${err != null ? "\nERROR: $err" : ""}"
|
if (v is List) return v;
|
||||||
"${msg != null ? "\nMESSAGE: $msg" : ""}"
|
|
||||||
"${detail != null ? "\nDETAIL: $detail" : ""}",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<_ApiResponse> listRestaurantsRaw() async {
|
static Map<String, dynamic> _requireJson(ApiRawResponse raw, String label) {
|
||||||
return _getJson("/businesses/list.cfm");
|
final j = raw.json;
|
||||||
|
if (j == null) {
|
||||||
|
throw StateError("$label request failed: ${raw.statusCode}\nNon-JSON response.");
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// Businesses (legacy model name: Restaurant)
|
||||||
|
// -------------------------
|
||||||
|
|
||||||
|
static Future<ApiRawResponse> listRestaurantsRaw() async {
|
||||||
|
return _getRaw("/businesses/list.cfm", businessIdOverride: _mvpBusinessId);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<Restaurant>> listRestaurants() async {
|
static Future<List<Restaurant>> listRestaurants() async {
|
||||||
final r = await listRestaurantsRaw();
|
final raw = await listRestaurantsRaw();
|
||||||
if (r.statusCode != 200) {
|
final j = _requireJson(raw, "Businesses");
|
||||||
throw StateError("Restaurants request failed: ${r.statusCode}\n${r.rawBody}");
|
|
||||||
}
|
|
||||||
|
|
||||||
final data = r.json;
|
if (!_ok(j)) {
|
||||||
if (data == null) {
|
|
||||||
throw StateError("Restaurants response was not JSON.\n${r.rawBody}");
|
|
||||||
}
|
|
||||||
|
|
||||||
_throwIfApiError(data, "Restaurants");
|
|
||||||
|
|
||||||
final rows = data["Businesses"];
|
|
||||||
if (rows is! List) {
|
|
||||||
throw StateError(
|
throw StateError(
|
||||||
"Restaurants JSON missing Businesses array.\nKeys present: ${data.keys.toList()}\nRaw: ${r.rawBody}",
|
"Businesses API returned OK=false\nERROR: ${_err(j)}\nDETAIL: ${(j["DETAIL"] ?? "").toString()}\nHTTP Status: ${raw.statusCode}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rows
|
final arr = _pickArray(j, const ["Businesses", "BUSINESSES"]);
|
||||||
.whereType<Map<String, dynamic>>()
|
if (arr == null) {
|
||||||
.map((e) => Restaurant.fromJson(e))
|
throw StateError("Businesses JSON missing Businesses array.\nRaw: ${raw.rawBody}");
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<_ApiResponse> listServicePointsRaw({required int businessId}) async {
|
final out = <Restaurant>[];
|
||||||
return _postJson("/servicepoints/list.cfm", {"BusinessID": businessId});
|
for (final e in arr) {
|
||||||
|
if (e is Map<String, dynamic>) {
|
||||||
|
out.add(Restaurant.fromJson(e));
|
||||||
|
} else if (e is Map) {
|
||||||
|
out.add(Restaurant.fromJson(e.cast<String, dynamic>()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// Service Points
|
||||||
|
// -------------------------
|
||||||
|
|
||||||
static Future<List<ServicePoint>> listServicePoints({required int businessId}) async {
|
static Future<List<ServicePoint>> listServicePoints({required int businessId}) async {
|
||||||
final r = await listServicePointsRaw(businessId: businessId);
|
// CRITICAL: endpoint is behaving like it reads JSON body, not query/header.
|
||||||
if (r.statusCode != 200) {
|
final raw = await _postRaw(
|
||||||
throw StateError("ServicePoints request failed: ${r.statusCode}\n${r.rawBody}");
|
"/servicepoints/list.cfm",
|
||||||
}
|
{"BusinessID": businessId},
|
||||||
|
businessIdOverride: businessId,
|
||||||
|
);
|
||||||
|
|
||||||
final data = r.json;
|
final j = _requireJson(raw, "ServicePoints");
|
||||||
if (data == null) {
|
|
||||||
throw StateError("ServicePoints response was not JSON.\n${r.rawBody}");
|
|
||||||
}
|
|
||||||
|
|
||||||
_throwIfApiError(data, "ServicePoints");
|
if (!_ok(j)) {
|
||||||
|
|
||||||
// STRICT: hump case
|
|
||||||
final rows = data["ServicePoints"];
|
|
||||||
if (rows is! List) {
|
|
||||||
throw StateError(
|
throw StateError(
|
||||||
"ServicePoints JSON missing ServicePoints array.\nKeys present: ${data.keys.toList()}\nRaw: ${r.rawBody}",
|
"ServicePoints API returned OK=false\nERROR: ${_err(j)}\nDETAIL: ${(j["DETAIL"] ?? "").toString()}\nHTTP Status: ${raw.statusCode}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return rows
|
final arr = _pickArray(j, const ["ServicePoints", "SERVICEPOINTS"]);
|
||||||
.whereType<Map<String, dynamic>>()
|
if (arr == null) {
|
||||||
.map((e) => ServicePoint.fromJson(e))
|
throw StateError("ServicePoints JSON missing ServicePoints array.\nRaw: ${raw.rawBody}");
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ApiResponse {
|
final out = <ServicePoint>[];
|
||||||
final int statusCode;
|
for (final e in arr) {
|
||||||
final String rawBody;
|
if (e is Map<String, dynamic>) {
|
||||||
final Map<String, dynamic>? json;
|
out.add(ServicePoint.fromJson(e));
|
||||||
|
} else if (e is Map) {
|
||||||
const _ApiResponse({
|
out.add(ServicePoint.fromJson(e.cast<String, dynamic>()));
|
||||||
required this.statusCode,
|
}
|
||||||
required this.rawBody,
|
}
|
||||||
required this.json,
|
return out;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// Ordering API (stubs referenced by OrderHomeScreen)
|
||||||
|
// -------------------------
|
||||||
|
|
||||||
|
static Future<dynamic> listMenuItems({required int businessId}) async {
|
||||||
|
throw StateError("endpoint_not_implemented: Api.listMenuItems");
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<dynamic> getOrCreateCart({
|
||||||
|
required int userId,
|
||||||
|
required int businessId,
|
||||||
|
required int servicePointId,
|
||||||
|
required int orderTypeId,
|
||||||
|
}) async {
|
||||||
|
throw StateError("endpoint_not_implemented: Api.getOrCreateCart");
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<dynamic> getCart({required int orderId}) async {
|
||||||
|
throw StateError("endpoint_not_implemented: Api.getCart");
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> setLineItem({
|
||||||
|
required int orderId,
|
||||||
|
required int parentOrderLineItemId,
|
||||||
|
required int itemId,
|
||||||
|
required int qty,
|
||||||
|
required List<int> selectedChildItemIds,
|
||||||
|
}) async {
|
||||||
|
throw StateError("endpoint_not_implemented: Api.setLineItem");
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> submitOrder({required int orderId}) async {
|
||||||
|
throw StateError("endpoint_not_implemented: Api.submitOrder");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue