add start/business unit
This commit is contained in:
@@ -124,6 +124,7 @@ class HiveTypeIds {
|
||||
static const int promotionModel = 26;
|
||||
static const int categoryModel = 27;
|
||||
static const int favoriteModel = 28;
|
||||
static const int businessUnitModel = 29;
|
||||
|
||||
// Enums (30-59)
|
||||
static const int userRole = 30;
|
||||
|
||||
@@ -10,6 +10,8 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:worker/features/account/presentation/pages/addresses_page.dart';
|
||||
import 'package:worker/features/account/presentation/pages/change_password_page.dart';
|
||||
import 'package:worker/features/account/presentation/pages/profile_edit_page.dart';
|
||||
import 'package:worker/features/auth/domain/entities/business_unit.dart';
|
||||
import 'package:worker/features/auth/presentation/pages/business_unit_selection_page.dart';
|
||||
import 'package:worker/features/auth/presentation/pages/login_page.dart';
|
||||
import 'package:worker/features/auth/presentation/pages/register_page.dart';
|
||||
import 'package:worker/features/cart/presentation/pages/cart_page.dart';
|
||||
@@ -62,8 +64,30 @@ class AppRouter {
|
||||
GoRoute(
|
||||
path: RouteNames.register,
|
||||
name: RouteNames.register,
|
||||
pageBuilder: (context, state) =>
|
||||
MaterialPage(key: state.pageKey, child: const RegisterPage()),
|
||||
pageBuilder: (context, state) {
|
||||
final extra = state.extra as Map<String, dynamic>?;
|
||||
return MaterialPage(
|
||||
key: state.pageKey,
|
||||
child: RegisterPage(
|
||||
selectedBusinessUnit: extra?['businessUnit'] as BusinessUnit?,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: RouteNames.businessUnitSelection,
|
||||
name: RouteNames.businessUnitSelection,
|
||||
pageBuilder: (context, state) {
|
||||
final extra = state.extra as Map<String, dynamic>?;
|
||||
return MaterialPage(
|
||||
key: state.pageKey,
|
||||
child: BusinessUnitSelectionPage(
|
||||
businessUnits: extra?['businessUnits'] as List<BusinessUnit>?,
|
||||
isRegistrationFlow:
|
||||
(extra?['isRegistrationFlow'] as bool?) ?? false,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// Main Route (with bottom navigation)
|
||||
@@ -447,6 +471,7 @@ class RouteNames {
|
||||
static const String login = '/login';
|
||||
static const String otpVerification = '/otp-verification';
|
||||
static const String register = '/register';
|
||||
static const String businessUnitSelection = '/business-unit-selection';
|
||||
}
|
||||
|
||||
/// Route Extensions
|
||||
|
||||
91
lib/features/auth/data/models/business_unit_model.dart
Normal file
91
lib/features/auth/data/models/business_unit_model.dart
Normal file
@@ -0,0 +1,91 @@
|
||||
/// Business Unit Data Model
|
||||
///
|
||||
/// Hive model for local storage of business units.
|
||||
library;
|
||||
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import 'package:worker/core/constants/storage_constants.dart';
|
||||
import 'package:worker/features/auth/domain/entities/business_unit.dart';
|
||||
|
||||
part 'business_unit_model.g.dart';
|
||||
|
||||
/// Business Unit Model for Hive storage
|
||||
@HiveType(typeId: HiveTypeIds.businessUnitModel)
|
||||
class BusinessUnitModel extends HiveObject {
|
||||
/// Unique business unit identifier
|
||||
@HiveField(0)
|
||||
String id;
|
||||
|
||||
/// Business unit code (e.g., "VIKD", "HSKD", "LPKD")
|
||||
@HiveField(1)
|
||||
String code;
|
||||
|
||||
/// Display name
|
||||
@HiveField(2)
|
||||
String name;
|
||||
|
||||
/// Description
|
||||
@HiveField(3)
|
||||
String? description;
|
||||
|
||||
/// Whether this is the default unit
|
||||
@HiveField(4)
|
||||
bool isDefault;
|
||||
|
||||
BusinessUnitModel({
|
||||
required this.id,
|
||||
required this.code,
|
||||
required this.name,
|
||||
this.description,
|
||||
this.isDefault = false,
|
||||
});
|
||||
|
||||
/// Convert to domain entity
|
||||
BusinessUnit toEntity() {
|
||||
return BusinessUnit(
|
||||
id: id,
|
||||
code: code,
|
||||
name: name,
|
||||
description: description,
|
||||
isDefault: isDefault,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create from domain entity
|
||||
factory BusinessUnitModel.fromEntity(BusinessUnit entity) {
|
||||
return BusinessUnitModel(
|
||||
id: entity.id,
|
||||
code: entity.code,
|
||||
name: entity.name,
|
||||
description: entity.description,
|
||||
isDefault: entity.isDefault,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create from JSON
|
||||
factory BusinessUnitModel.fromJson(Map<String, dynamic> json) {
|
||||
return BusinessUnitModel(
|
||||
id: json['id'] as String,
|
||||
code: json['code'] as String,
|
||||
name: json['name'] as String,
|
||||
description: json['description'] as String?,
|
||||
isDefault: json['is_default'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert to JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'code': code,
|
||||
'name': name,
|
||||
'description': description,
|
||||
'is_default': isDefault,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BusinessUnitModel(id: $id, code: $code, name: $name)';
|
||||
}
|
||||
}
|
||||
53
lib/features/auth/data/models/business_unit_model.g.dart
Normal file
53
lib/features/auth/data/models/business_unit_model.g.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'business_unit_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class BusinessUnitModelAdapter extends TypeAdapter<BusinessUnitModel> {
|
||||
@override
|
||||
final typeId = 29;
|
||||
|
||||
@override
|
||||
BusinessUnitModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return BusinessUnitModel(
|
||||
id: fields[0] as String,
|
||||
code: fields[1] as String,
|
||||
name: fields[2] as String,
|
||||
description: fields[3] as String?,
|
||||
isDefault: fields[4] == null ? false : fields[4] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, BusinessUnitModel obj) {
|
||||
writer
|
||||
..writeByte(5)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.code)
|
||||
..writeByte(2)
|
||||
..write(obj.name)
|
||||
..writeByte(3)
|
||||
..write(obj.description)
|
||||
..writeByte(4)
|
||||
..write(obj.isDefault);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is BusinessUnitModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
93
lib/features/auth/domain/entities/business_unit.dart
Normal file
93
lib/features/auth/domain/entities/business_unit.dart
Normal file
@@ -0,0 +1,93 @@
|
||||
/// Domain Entity: Business Unit
|
||||
///
|
||||
/// Represents a business unit that a user can access.
|
||||
library;
|
||||
|
||||
/// Business Unit Entity
|
||||
///
|
||||
/// Represents a business division or unit that a user has access to.
|
||||
class BusinessUnit {
|
||||
/// Unique business unit identifier
|
||||
final String id;
|
||||
|
||||
/// Business unit code (e.g., "VIKD", "HSKD", "LPKD")
|
||||
final String code;
|
||||
|
||||
/// Display name
|
||||
final String name;
|
||||
|
||||
/// Description
|
||||
final String? description;
|
||||
|
||||
/// Whether this is the default unit
|
||||
final bool isDefault;
|
||||
|
||||
const BusinessUnit({
|
||||
required this.id,
|
||||
required this.code,
|
||||
required this.name,
|
||||
this.description,
|
||||
this.isDefault = false,
|
||||
});
|
||||
|
||||
/// Create from JSON map
|
||||
factory BusinessUnit.fromJson(Map<String, dynamic> json) {
|
||||
return BusinessUnit(
|
||||
id: json['id'] as String,
|
||||
code: json['code'] as String,
|
||||
name: json['name'] as String,
|
||||
description: json['description'] as String?,
|
||||
isDefault: json['is_default'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert to JSON map
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'code': code,
|
||||
'name': name,
|
||||
'description': description,
|
||||
'is_default': isDefault,
|
||||
};
|
||||
}
|
||||
|
||||
/// Copy with method for immutability
|
||||
BusinessUnit copyWith({
|
||||
String? id,
|
||||
String? code,
|
||||
String? name,
|
||||
String? description,
|
||||
bool? isDefault,
|
||||
}) {
|
||||
return BusinessUnit(
|
||||
id: id ?? this.id,
|
||||
code: code ?? this.code,
|
||||
name: name ?? this.name,
|
||||
description: description ?? this.description,
|
||||
isDefault: isDefault ?? this.isDefault,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is BusinessUnit &&
|
||||
other.id == id &&
|
||||
other.code == code &&
|
||||
other.name == name &&
|
||||
other.description == description &&
|
||||
other.isDefault == isDefault;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return Object.hash(id, code, name, description, isDefault);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BusinessUnit(id: $id, code: $code, name: $name, isDefault: $isDefault)';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
/// Business Unit Selection Page
|
||||
///
|
||||
/// Allows users to select a business unit during registration or after login
|
||||
/// when they have access to multiple units.
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/router/app_router.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/features/auth/domain/entities/business_unit.dart';
|
||||
|
||||
/// Business Unit Selection Page
|
||||
///
|
||||
/// Flow:
|
||||
/// 1. During registration: User selects unit before going to register page
|
||||
/// 2. After login: User selects unit if they have multiple units
|
||||
class BusinessUnitSelectionPage extends StatefulWidget {
|
||||
const BusinessUnitSelectionPage({
|
||||
super.key,
|
||||
this.businessUnits,
|
||||
this.isRegistrationFlow = false,
|
||||
this.onUnitSelected,
|
||||
});
|
||||
|
||||
/// List of available business units
|
||||
final List<BusinessUnit>? businessUnits;
|
||||
|
||||
/// Whether this is part of registration flow
|
||||
final bool isRegistrationFlow;
|
||||
|
||||
/// Callback when business unit is selected (for registration flow)
|
||||
final void Function(BusinessUnit)? onUnitSelected;
|
||||
|
||||
@override
|
||||
State<BusinessUnitSelectionPage> createState() =>
|
||||
_BusinessUnitSelectionPageState();
|
||||
}
|
||||
|
||||
class _BusinessUnitSelectionPageState extends State<BusinessUnitSelectionPage> {
|
||||
BusinessUnit? _selectedUnit;
|
||||
late List<BusinessUnit> _availableUnits;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Use provided units or mock data for registration flow
|
||||
_availableUnits = widget.businessUnits ?? _getMockBusinessUnits();
|
||||
}
|
||||
|
||||
/// Mock business units for registration flow
|
||||
/// TODO: Replace with actual API data when backend is ready
|
||||
List<BusinessUnit> _getMockBusinessUnits() {
|
||||
return [
|
||||
const BusinessUnit(
|
||||
id: '1',
|
||||
code: 'VIKD',
|
||||
name: 'VIKD',
|
||||
description: 'Đơn vị kinh doanh VIKD',
|
||||
),
|
||||
const BusinessUnit(
|
||||
id: '2',
|
||||
code: 'HSKD',
|
||||
name: 'HSKD',
|
||||
description: 'Đơn vị kinh doanh HSKD',
|
||||
),
|
||||
const BusinessUnit(
|
||||
id: '3',
|
||||
code: 'LPKD',
|
||||
name: 'LPKD',
|
||||
description: 'Đơn vị kinh doanh LPKD',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
void _handleContinue() {
|
||||
if (_selectedUnit == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: const Text('Vui lòng chọn đơn vị kinh doanh'),
|
||||
backgroundColor: AppColors.danger,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (widget.isRegistrationFlow) {
|
||||
// Registration flow: pass selected unit to register page
|
||||
widget.onUnitSelected?.call(_selectedUnit!);
|
||||
context.pushNamed(
|
||||
RouteNames.register,
|
||||
extra: {'businessUnit': _selectedUnit},
|
||||
);
|
||||
} else {
|
||||
// Login flow: save selected unit and navigate to home
|
||||
// TODO: Save selected unit to local storage/state
|
||||
context.goNamed(RouteNames.home);
|
||||
}
|
||||
}
|
||||
|
||||
void _showInfoDialog() {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Đơn vị kinh doanh'),
|
||||
content: const Text(
|
||||
'Chọn đơn vị kinh doanh mà bạn muốn truy cập. '
|
||||
'Bạn có thể thay đổi đơn vị sau khi đăng nhập.',
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Đóng'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.white,
|
||||
appBar: AppBar(
|
||||
backgroundColor: AppColors.white,
|
||||
elevation: AppBarSpecs.elevation,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back, color: Colors.black),
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
title: const Text(
|
||||
'Đơn vị kinh doanh',
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.info_outline, color: Colors.black),
|
||||
onPressed: _showInfoDialog,
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Logo Section
|
||||
Center(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [AppColors.primaryBlue, AppColors.lightBlue],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'DBIZ',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Worker App',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
|
||||
// Welcome Message
|
||||
const Text(
|
||||
'Chọn đơn vị kinh doanh để tiếp tục',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: AppColors.grey500, fontSize: 14),
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// Business Unit Selection List
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: AppSpacing.sm),
|
||||
child: Text(
|
||||
'Đơn vị kinh doanh',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
// Business Unit List Tiles
|
||||
...(_availableUnits.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final unit = entry.value;
|
||||
final isSelected = _selectedUnit?.id == unit.id;
|
||||
final isFirst = index == 0;
|
||||
final isLast = index == _availableUnits.length - 1;
|
||||
|
||||
return Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: isLast ? 0 : AppSpacing.xs,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? AppColors.primaryBlue
|
||||
: AppColors.grey100,
|
||||
width: isSelected ? 2 : 1,
|
||||
),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: isFirst
|
||||
? const Radius.circular(
|
||||
InputFieldSpecs.borderRadius,
|
||||
)
|
||||
: Radius.zero,
|
||||
bottom: isLast
|
||||
? const Radius.circular(
|
||||
InputFieldSpecs.borderRadius,
|
||||
)
|
||||
: Radius.zero,
|
||||
),
|
||||
boxShadow: isSelected
|
||||
? [
|
||||
BoxShadow(
|
||||
color: AppColors.primaryBlue.withValues(
|
||||
alpha: 0.1,
|
||||
),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_selectedUnit = unit;
|
||||
});
|
||||
},
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: isFirst
|
||||
? const Radius.circular(
|
||||
InputFieldSpecs.borderRadius,
|
||||
)
|
||||
: Radius.zero,
|
||||
bottom: isLast
|
||||
? const Radius.circular(
|
||||
InputFieldSpecs.borderRadius,
|
||||
)
|
||||
: Radius.zero,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.lg,
|
||||
vertical: AppSpacing.md,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Icon
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? AppColors.primaryBlue.withValues(
|
||||
alpha: 0.1,
|
||||
)
|
||||
: AppColors.grey50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.business,
|
||||
color: isSelected
|
||||
? AppColors.primaryBlue
|
||||
: AppColors.grey500,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.md),
|
||||
// Unit Name
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
unit.name,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: isSelected
|
||||
? FontWeight.w600
|
||||
: FontWeight.w500,
|
||||
color: isSelected
|
||||
? AppColors.primaryBlue
|
||||
: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
if (unit.description != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
unit.description!,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
// Radio indicator
|
||||
Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: isSelected
|
||||
? AppColors.primaryBlue
|
||||
: AppColors.grey500,
|
||||
width: 2,
|
||||
),
|
||||
color: isSelected
|
||||
? AppColors.primaryBlue
|
||||
: Colors.transparent,
|
||||
),
|
||||
child: isSelected
|
||||
? const Icon(
|
||||
Icons.circle,
|
||||
size: 10,
|
||||
color: AppColors.white,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList()),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
|
||||
// Continue Button
|
||||
SizedBox(
|
||||
height: ButtonSpecs.height,
|
||||
child: ElevatedButton(
|
||||
onPressed: _handleContinue,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primaryBlue,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
ButtonSpecs.borderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Tiếp tục',
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -115,15 +115,12 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Navigate to register page
|
||||
/// Navigate to business unit selection (registration flow)
|
||||
void _navigateToRegister() {
|
||||
// TODO: Navigate to register page when route is set up
|
||||
// context.go('/register');
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Chức năng đăng ký đang được phát triển'),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
// Navigate to business unit selection page first
|
||||
context.pushNamed(
|
||||
RouteNames.businessUnitSelection,
|
||||
extra: {'isRegistrationFlow': true},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:image_picker/image_picker.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/core/utils/validators.dart';
|
||||
import 'package:worker/features/auth/domain/entities/business_unit.dart';
|
||||
import 'package:worker/features/auth/presentation/widgets/phone_input_field.dart';
|
||||
import 'package:worker/features/auth/presentation/widgets/file_upload_card.dart';
|
||||
import 'package:worker/features/auth/presentation/widgets/role_dropdown.dart';
|
||||
@@ -29,10 +30,13 @@ import 'package:worker/features/auth/presentation/widgets/role_dropdown.dart';
|
||||
/// - Terms and conditions checkbox
|
||||
///
|
||||
/// Navigation:
|
||||
/// - From: Login page
|
||||
/// - From: Business unit selection page
|
||||
/// - To: OTP verification (broker/other) or pending approval (worker/dealer)
|
||||
class RegisterPage extends ConsumerStatefulWidget {
|
||||
const RegisterPage({super.key});
|
||||
/// Selected business unit from previous screen
|
||||
final BusinessUnit? selectedBusinessUnit;
|
||||
|
||||
const RegisterPage({super.key, this.selectedBusinessUnit});
|
||||
|
||||
@override
|
||||
ConsumerState<RegisterPage> createState() => _RegisterPageState();
|
||||
@@ -235,6 +239,18 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
|
||||
|
||||
try {
|
||||
// TODO: Implement actual registration API call
|
||||
// Include widget.selectedBusinessUnit?.id in the API request
|
||||
// Example:
|
||||
// final result = await authRepository.register(
|
||||
// fullName: _fullNameController.text.trim(),
|
||||
// phone: _phoneController.text.trim(),
|
||||
// email: _emailController.text.trim(),
|
||||
// password: _passwordController.text,
|
||||
// role: _selectedRole,
|
||||
// businessUnitId: widget.selectedBusinessUnit?.id,
|
||||
// ...
|
||||
// );
|
||||
|
||||
// For now, simulate API delay
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:worker/core/database/models/cached_data.dart';
|
||||
import 'package:worker/core/database/models/enums.dart';
|
||||
import 'package:worker/features/account/data/models/audit_log_model.dart';
|
||||
import 'package:worker/features/account/data/models/payment_reminder_model.dart';
|
||||
import 'package:worker/features/auth/data/models/business_unit_model.dart';
|
||||
import 'package:worker/features/auth/data/models/user_model.dart';
|
||||
import 'package:worker/features/auth/data/models/user_session_model.dart';
|
||||
import 'package:worker/features/cart/data/models/cart_item_model.dart';
|
||||
@@ -37,6 +38,7 @@ import 'package:worker/features/showrooms/data/models/showroom_product_model.dar
|
||||
extension HiveRegistrar on HiveInterface {
|
||||
void registerAdapters() {
|
||||
registerAdapter(AuditLogModelAdapter());
|
||||
registerAdapter(BusinessUnitModelAdapter());
|
||||
registerAdapter(CachedDataAdapter());
|
||||
registerAdapter(CartItemModelAdapter());
|
||||
registerAdapter(CartModelAdapter());
|
||||
@@ -92,6 +94,7 @@ extension HiveRegistrar on HiveInterface {
|
||||
extension IsolatedHiveRegistrar on IsolatedHiveInterface {
|
||||
void registerAdapters() {
|
||||
registerAdapter(AuditLogModelAdapter());
|
||||
registerAdapter(BusinessUnitModelAdapter());
|
||||
registerAdapter(CachedDataAdapter());
|
||||
registerAdapter(CartItemModelAdapter());
|
||||
registerAdapter(CartModelAdapter());
|
||||
|
||||
Reference in New Issue
Block a user