add start/business unit
This commit is contained in:
@@ -124,6 +124,7 @@ class HiveTypeIds {
|
|||||||
static const int promotionModel = 26;
|
static const int promotionModel = 26;
|
||||||
static const int categoryModel = 27;
|
static const int categoryModel = 27;
|
||||||
static const int favoriteModel = 28;
|
static const int favoriteModel = 28;
|
||||||
|
static const int businessUnitModel = 29;
|
||||||
|
|
||||||
// Enums (30-59)
|
// Enums (30-59)
|
||||||
static const int userRole = 30;
|
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/addresses_page.dart';
|
||||||
import 'package:worker/features/account/presentation/pages/change_password_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/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/login_page.dart';
|
||||||
import 'package:worker/features/auth/presentation/pages/register_page.dart';
|
import 'package:worker/features/auth/presentation/pages/register_page.dart';
|
||||||
import 'package:worker/features/cart/presentation/pages/cart_page.dart';
|
import 'package:worker/features/cart/presentation/pages/cart_page.dart';
|
||||||
@@ -62,8 +64,30 @@ class AppRouter {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: RouteNames.register,
|
path: RouteNames.register,
|
||||||
name: RouteNames.register,
|
name: RouteNames.register,
|
||||||
pageBuilder: (context, state) =>
|
pageBuilder: (context, state) {
|
||||||
MaterialPage(key: state.pageKey, child: const RegisterPage()),
|
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)
|
// Main Route (with bottom navigation)
|
||||||
@@ -447,6 +471,7 @@ class RouteNames {
|
|||||||
static const String login = '/login';
|
static const String login = '/login';
|
||||||
static const String otpVerification = '/otp-verification';
|
static const String otpVerification = '/otp-verification';
|
||||||
static const String register = '/register';
|
static const String register = '/register';
|
||||||
|
static const String businessUnitSelection = '/business-unit-selection';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Route Extensions
|
/// 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() {
|
void _navigateToRegister() {
|
||||||
// TODO: Navigate to register page when route is set up
|
// Navigate to business unit selection page first
|
||||||
// context.go('/register');
|
context.pushNamed(
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
RouteNames.businessUnitSelection,
|
||||||
const SnackBar(
|
extra: {'isRegistrationFlow': true},
|
||||||
content: Text('Chức năng đăng ký đang được phát triển'),
|
|
||||||
behavior: SnackBarBehavior.floating,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import 'package:image_picker/image_picker.dart';
|
|||||||
import 'package:worker/core/constants/ui_constants.dart';
|
import 'package:worker/core/constants/ui_constants.dart';
|
||||||
import 'package:worker/core/theme/colors.dart';
|
import 'package:worker/core/theme/colors.dart';
|
||||||
import 'package:worker/core/utils/validators.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/phone_input_field.dart';
|
||||||
import 'package:worker/features/auth/presentation/widgets/file_upload_card.dart';
|
import 'package:worker/features/auth/presentation/widgets/file_upload_card.dart';
|
||||||
import 'package:worker/features/auth/presentation/widgets/role_dropdown.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
|
/// - Terms and conditions checkbox
|
||||||
///
|
///
|
||||||
/// Navigation:
|
/// Navigation:
|
||||||
/// - From: Login page
|
/// - From: Business unit selection page
|
||||||
/// - To: OTP verification (broker/other) or pending approval (worker/dealer)
|
/// - To: OTP verification (broker/other) or pending approval (worker/dealer)
|
||||||
class RegisterPage extends ConsumerStatefulWidget {
|
class RegisterPage extends ConsumerStatefulWidget {
|
||||||
const RegisterPage({super.key});
|
/// Selected business unit from previous screen
|
||||||
|
final BusinessUnit? selectedBusinessUnit;
|
||||||
|
|
||||||
|
const RegisterPage({super.key, this.selectedBusinessUnit});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<RegisterPage> createState() => _RegisterPageState();
|
ConsumerState<RegisterPage> createState() => _RegisterPageState();
|
||||||
@@ -235,6 +239,18 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// TODO: Implement actual registration API call
|
// 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
|
// For now, simulate API delay
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
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/core/database/models/enums.dart';
|
||||||
import 'package:worker/features/account/data/models/audit_log_model.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/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_model.dart';
|
||||||
import 'package:worker/features/auth/data/models/user_session_model.dart';
|
import 'package:worker/features/auth/data/models/user_session_model.dart';
|
||||||
import 'package:worker/features/cart/data/models/cart_item_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 {
|
extension HiveRegistrar on HiveInterface {
|
||||||
void registerAdapters() {
|
void registerAdapters() {
|
||||||
registerAdapter(AuditLogModelAdapter());
|
registerAdapter(AuditLogModelAdapter());
|
||||||
|
registerAdapter(BusinessUnitModelAdapter());
|
||||||
registerAdapter(CachedDataAdapter());
|
registerAdapter(CachedDataAdapter());
|
||||||
registerAdapter(CartItemModelAdapter());
|
registerAdapter(CartItemModelAdapter());
|
||||||
registerAdapter(CartModelAdapter());
|
registerAdapter(CartModelAdapter());
|
||||||
@@ -92,6 +94,7 @@ extension HiveRegistrar on HiveInterface {
|
|||||||
extension IsolatedHiveRegistrar on IsolatedHiveInterface {
|
extension IsolatedHiveRegistrar on IsolatedHiveInterface {
|
||||||
void registerAdapters() {
|
void registerAdapters() {
|
||||||
registerAdapter(AuditLogModelAdapter());
|
registerAdapter(AuditLogModelAdapter());
|
||||||
|
registerAdapter(BusinessUnitModelAdapter());
|
||||||
registerAdapter(CachedDataAdapter());
|
registerAdapter(CachedDataAdapter());
|
||||||
registerAdapter(CartItemModelAdapter());
|
registerAdapter(CartItemModelAdapter());
|
||||||
registerAdapter(CartModelAdapter());
|
registerAdapter(CartModelAdapter());
|
||||||
|
|||||||
Reference in New Issue
Block a user