/// Page: Model Houses Page /// /// Displays model house library and design requests following html/nha-mau.html. library; import 'package:cached_network_image/cached_network_image.dart'; import 'package:worker/core/widgets/loading_indicator.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.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/showrooms/domain/entities/design_request.dart'; import 'package:worker/features/showrooms/domain/entities/sample_project.dart'; import 'package:worker/features/showrooms/presentation/providers/design_request_provider.dart'; import 'package:worker/features/showrooms/presentation/providers/sample_project_provider.dart'; /// Model Houses Page /// /// Two tabs: /// 1. Thư viện mẫu - Model house library with 360° views /// 2. Yêu cầu thiết kế - Design requests with status tracking class ModelHousesPage extends ConsumerStatefulWidget { const ModelHousesPage({super.key}); @override ConsumerState createState() => _ModelHousesPageState(); } class _ModelHousesPageState extends ConsumerState with SingleTickerProviderStateMixin { late TabController _tabController; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); } @override void dispose() { _tabController.dispose(); super.dispose(); } void _showInfoDialog() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text( 'Hướng dẫn sử dụng', style: TextStyle(fontWeight: FontWeight.bold), ), content: const SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text('Đây là nội dung hướng dẫn sử dụng cho tính năng Nhà mẫu:'), SizedBox(height: 12), Text( '• Tab "Thư viện Mẫu 360": Là nơi công ty cung cấp các mẫu thiết kế 360° có sẵn để bạn tham khảo.', ), SizedBox(height: 8), Text( '• Tab "Yêu cầu Thiết kế": Là nơi bạn gửi yêu cầu (ticket) để đội ngũ thiết kế của chúng tôi hỗ trợ bạn.', ), SizedBox(height: 8), Text( '• Bấm nút "+" trong tab "Yêu cầu Thiết kế" để tạo một Yêu cầu Thiết kế mới.', ), SizedBox(height: 8), Text( '• Khi yêu cầu hoàn thành, bạn có thể xem link thiết kế 3D trong trang chi tiết yêu cầu.', ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Đóng'), ), ], ), ); } void _createNewRequest() { context.push(RouteNames.designRequestCreate); } @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Scaffold( backgroundColor: colorScheme.surfaceContainerLowest, appBar: AppBar( backgroundColor: colorScheme.surface, elevation: AppBarSpecs.elevation, leading: IconButton( icon: Icon(Icons.arrow_back, color: colorScheme.onSurface), onPressed: () => Navigator.of(context).pop(), ), centerTitle: false, title: Text( 'Nhà mẫu', style: TextStyle( color: colorScheme.onSurface, fontSize: 20, fontWeight: FontWeight.w600, ), ), actions: [ IconButton( icon: Icon(Icons.info_outline, color: colorScheme.onSurface), onPressed: _showInfoDialog, ), const SizedBox(width: AppSpacing.sm), ], bottom: TabBar( controller: _tabController, indicatorColor: colorScheme.primary, indicatorWeight: 3, labelColor: colorScheme.primary, labelStyle: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), unselectedLabelColor: colorScheme.onSurfaceVariant, unselectedLabelStyle: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, ), tabs: const [ Tab(text: 'Thư viện mẫu'), Tab(text: 'Yêu cầu thiết kế'), ], ), ), body: TabBarView( controller: _tabController, children: const [_LibraryTab(), _DesignRequestsTab()], ), floatingActionButton: AnimatedBuilder( animation: _tabController, builder: (context, child) { // Show FAB only on Design Requests tab return _tabController.index == 1 ? FloatingActionButton( onPressed: _createNewRequest, backgroundColor: colorScheme.primary, elevation: 4, child: Icon( Icons.add, color: colorScheme.onPrimary, size: 28, ), ) : const SizedBox.shrink(); }, ), ); } } /// Library Tab - Model house 360° library class _LibraryTab extends ConsumerWidget { const _LibraryTab(); @override Widget build(BuildContext context, WidgetRef ref) { final colorScheme = Theme.of(context).colorScheme; final sampleProjectsAsync = ref.watch(sampleProjectsListProvider); return sampleProjectsAsync.when( data: (projects) { if (projects.isEmpty) { return Center( child: Padding( padding: const EdgeInsets.all(40), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.home_work_outlined, size: 64, color: colorScheme.onSurfaceVariant, ), const SizedBox(height: 16), Text( 'Chưa có mẫu nhà nào', style: TextStyle( fontSize: 16, color: colorScheme.onSurfaceVariant, ), ), ], ), ), ); } return RefreshIndicator( onRefresh: () => ref.read(sampleProjectsListProvider.notifier).refresh(), child: ListView.builder( padding: const EdgeInsets.all(20), itemCount: projects.length, itemBuilder: (context, index) { final project = projects[index]; return _LibraryCard(project: project); }, ), ); }, loading: () => const const CustomLoadingIndicator(), error: (error, stack) => Center( child: Padding( padding: const EdgeInsets.all(40), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.error_outline, size: 64, color: AppColors.danger, ), const SizedBox(height: 16), Text( 'Lỗi tải dữ liệu: ${error.toString().replaceAll('Exception: ', '')}', textAlign: TextAlign.center, style: TextStyle( fontSize: 14, color: colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 16), ElevatedButton( onPressed: () => ref.invalidate(sampleProjectsListProvider), child: const Text('Thử lại'), ), ], ), ), ), ); } } /// Library Card Widget class _LibraryCard extends StatelessWidget { const _LibraryCard({required this.project}); final SampleProject project; @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Card( elevation: 2, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), margin: const EdgeInsets.only(bottom: 20), child: InkWell( onTap: () { context.push('/model-houses/${project.id}'); }, borderRadius: BorderRadius.circular(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Image with 360 badge Stack( children: [ ClipRRect( borderRadius: const BorderRadius.vertical( top: Radius.circular(12), ), child: project.thumbnailUrl != null ? CachedNetworkImage( imageUrl: project.thumbnailUrl!, width: double.infinity, height: 200, fit: BoxFit.cover, placeholder: (context, url) => Container( height: 200, color: colorScheme.surfaceContainerHighest, child: const const CustomLoadingIndicator(), ), errorWidget: (context, url, error) => Container( height: 200, color: colorScheme.surfaceContainerHighest, child: Icon( Icons.image_not_supported, size: 48, color: colorScheme.onSurfaceVariant, ), ), ) : Container( height: 200, color: colorScheme.surfaceContainerHighest, child: Icon( Icons.home_work, size: 48, color: colorScheme.onSurfaceVariant, ), ), ), if (project.has360View) Positioned( top: 12, right: 12, child: Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: colorScheme.primary.withValues(alpha: 0.9), borderRadius: BorderRadius.circular(16), ), child: Text( 'Xem 360°', style: TextStyle( color: colorScheme.onPrimary, fontSize: 12, fontWeight: FontWeight.w600, ), ), ), ), ], ), // Content Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Title Text( project.projectName, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w700, color: colorScheme.onSurface, ), ), if (project.plainDescription.isNotEmpty) ...[ const SizedBox(height: 12), // Description Text( project.plainDescription, style: TextStyle( fontSize: 14, color: colorScheme.onSurfaceVariant, height: 1.5, ), maxLines: 3, overflow: TextOverflow.ellipsis, ), ], ], ), ), ], ), ), ); } } /// Design Requests Tab class _DesignRequestsTab extends ConsumerWidget { const _DesignRequestsTab(); @override Widget build(BuildContext context, WidgetRef ref) { final colorScheme = Theme.of(context).colorScheme; final requestsAsync = ref.watch(designRequestsListProvider); return requestsAsync.when( data: (requests) { if (requests.isEmpty) { return Center( child: Padding( padding: const EdgeInsets.all(40), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.design_services_outlined, size: 64, color: colorScheme.onSurfaceVariant, ), const SizedBox(height: 16), Text( 'Chưa có yêu cầu thiết kế nào', style: TextStyle( fontSize: 16, color: colorScheme.onSurfaceVariant, ), ), ], ), ), ); } return RefreshIndicator( onRefresh: () => ref.read(designRequestsListProvider.notifier).refresh(), child: ListView.builder( padding: const EdgeInsets.all(20), itemCount: requests.length, itemBuilder: (context, index) { final request = requests[index]; return _RequestCard(request: request); }, ), ); }, loading: () => const const CustomLoadingIndicator(), error: (error, stack) => Center( child: Padding( padding: const EdgeInsets.all(40), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.error_outline, size: 64, color: AppColors.danger, ), const SizedBox(height: 16), Text( 'Lỗi tải dữ liệu: ${error.toString().replaceAll('Exception: ', '')}', textAlign: TextAlign.center, style: TextStyle( fontSize: 14, color: colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 16), ElevatedButton( onPressed: () => ref.invalidate(designRequestsListProvider), child: const Text('Thử lại'), ), ], ), ), ), ); } } /// Request Card Widget class _RequestCard extends StatelessWidget { const _RequestCard({required this.request}); final DesignRequest request; Color _getStatusColor() { switch (request.status) { case DesignRequestStatus.pending: return const Color(0xFFffc107); // Warning yellow case DesignRequestStatus.designing: return const Color(0xFF3730a3); // Indigo case DesignRequestStatus.completed: return const Color(0xFF065f46); // Success green case DesignRequestStatus.rejected: return const Color(0xFFdc2626); // Danger red } } Color _getStatusBackgroundColor() { switch (request.status) { case DesignRequestStatus.pending: return const Color(0xFFfef3c7); // Light yellow case DesignRequestStatus.designing: return const Color(0xFFe0e7ff); // Light indigo case DesignRequestStatus.completed: return const Color(0xFFd1fae5); // Light green case DesignRequestStatus.rejected: return const Color(0xFFfee2e2); // Light red } } @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Card( elevation: 2, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), margin: const EdgeInsets.only(bottom: 16), child: InkWell( onTap: () { context.push('/model-houses/design-request/${request.id}'); }, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header: Code and Status Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( 'Mã yêu cầu: #${request.id}', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: colorScheme.onSurface, ), overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: _getStatusBackgroundColor(), borderRadius: BorderRadius.circular(20), ), child: Text( request.statusText.toUpperCase(), style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: _getStatusColor(), ), ), ), ], ), const SizedBox(height: 8), // Date if (request.dateline != null) Text( 'Deadline: ${request.dateline}', style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant), ), const SizedBox(height: 8), // Subject Text( request.subject, style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: colorScheme.onSurface, ), ), if (request.plainDescription.isNotEmpty) ...[ const SizedBox(height: 4), // Description Text( request.plainDescription, style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ], ), ), ), ); } }