add quotes
This commit is contained in:
@@ -27,6 +27,9 @@ class HiveBoxNames {
|
|||||||
/// Construction projects
|
/// Construction projects
|
||||||
static const String projectBox = 'project_box';
|
static const String projectBox = 'project_box';
|
||||||
|
|
||||||
|
/// Quote requests and quotations
|
||||||
|
static const String quotes = 'quotes_box';
|
||||||
|
|
||||||
/// Loyalty program transactions and points history
|
/// Loyalty program transactions and points history
|
||||||
static const String loyaltyBox = 'loyalty_box';
|
static const String loyaltyBox = 'loyalty_box';
|
||||||
|
|
||||||
@@ -61,6 +64,7 @@ class HiveBoxNames {
|
|||||||
cartBox,
|
cartBox,
|
||||||
orderBox,
|
orderBox,
|
||||||
projectBox,
|
projectBox,
|
||||||
|
quotes,
|
||||||
loyaltyBox,
|
loyaltyBox,
|
||||||
rewardsBox,
|
rewardsBox,
|
||||||
settingsBox,
|
settingsBox,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import 'package:worker/features/orders/presentation/pages/payments_page.dart';
|
|||||||
import 'package:worker/features/products/presentation/pages/product_detail_page.dart';
|
import 'package:worker/features/products/presentation/pages/product_detail_page.dart';
|
||||||
import 'package:worker/features/products/presentation/pages/products_page.dart';
|
import 'package:worker/features/products/presentation/pages/products_page.dart';
|
||||||
import 'package:worker/features/promotions/presentation/pages/promotion_detail_page.dart';
|
import 'package:worker/features/promotions/presentation/pages/promotion_detail_page.dart';
|
||||||
|
import 'package:worker/features/quotes/presentation/pages/quotes_page.dart';
|
||||||
|
|
||||||
/// App Router
|
/// App Router
|
||||||
///
|
///
|
||||||
@@ -156,6 +157,16 @@ class AppRouter {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Quotes Route
|
||||||
|
GoRoute(
|
||||||
|
path: RouteNames.quotes,
|
||||||
|
name: RouteNames.quotes,
|
||||||
|
pageBuilder: (context, state) => MaterialPage(
|
||||||
|
key: state.pageKey,
|
||||||
|
child: const QuotesPage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// TODO: Add more routes as features are implemented
|
// TODO: Add more routes as features are implemented
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -182,8 +182,7 @@ class HomePage extends ConsumerWidget {
|
|||||||
QuickAction(
|
QuickAction(
|
||||||
icon: Icons.description,
|
icon: Icons.description,
|
||||||
label: 'Yêu cầu báo giá',
|
label: 'Yêu cầu báo giá',
|
||||||
onTap: () =>
|
onTap: () => context.push(RouteNames.quotes),
|
||||||
_showComingSoon(context, 'Yêu cầu báo giá', l10n),
|
|
||||||
),
|
),
|
||||||
QuickAction(
|
QuickAction(
|
||||||
icon: Icons.inventory_2,
|
icon: Icons.inventory_2,
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ final class TotalInvoicesAmountProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$totalInvoicesAmountHash() =>
|
String _$totalInvoicesAmountHash() =>
|
||||||
r'7800e2be935dfe91d382957539b151bbf4f936fe';
|
r'f311abe063d4f9c2e25a13acac6202b1cce72754';
|
||||||
|
|
||||||
/// Total Unpaid Amount Provider
|
/// Total Unpaid Amount Provider
|
||||||
///
|
///
|
||||||
@@ -384,4 +384,4 @@ final class TotalUnpaidAmountProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$totalUnpaidAmountHash() => r'9a81800149d8809e1c3be065bc3c5357792c4aee';
|
String _$totalUnpaidAmountHash() => r'8cde697cbede5d799208fe5eca0cc0822ca608a4';
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
/// Data Source: Quotes Local Data Source
|
||||||
|
///
|
||||||
|
/// Handles local storage operations for quotes using Hive.
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:hive_ce/hive.dart';
|
||||||
|
import 'package:worker/core/constants/storage_constants.dart';
|
||||||
|
import 'package:worker/core/database/models/enums.dart';
|
||||||
|
import 'package:worker/features/quotes/data/models/quote_model.dart';
|
||||||
|
|
||||||
|
/// Quotes Local Data Source
|
||||||
|
///
|
||||||
|
/// Provides methods to interact with locally stored quote data.
|
||||||
|
class QuotesLocalDataSource {
|
||||||
|
Box<QuoteModel>? _quotesBox;
|
||||||
|
|
||||||
|
/// Get Hive box for quotes
|
||||||
|
Future<Box<QuoteModel>> get quotesBox async {
|
||||||
|
_quotesBox ??= await Hive.openBox<QuoteModel>(HiveBoxNames.quotes);
|
||||||
|
return _quotesBox!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all quotes
|
||||||
|
Future<List<QuoteModel>> getAllQuotes() async {
|
||||||
|
final box = await quotesBox;
|
||||||
|
return box.values.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get quote by ID
|
||||||
|
Future<QuoteModel?> getQuoteById(String quoteId) async {
|
||||||
|
final box = await quotesBox;
|
||||||
|
return box.values.firstWhere(
|
||||||
|
(quote) => quote.quoteId == quoteId,
|
||||||
|
orElse: () => throw Exception('Quote not found'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save quote
|
||||||
|
Future<void> saveQuote(QuoteModel quote) async {
|
||||||
|
final box = await quotesBox;
|
||||||
|
await box.put(quote.quoteId, quote);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save multiple quotes
|
||||||
|
Future<void> saveQuotes(List<QuoteModel> quotes) async {
|
||||||
|
final box = await quotesBox;
|
||||||
|
final Map<String, QuoteModel> quotesMap = {
|
||||||
|
for (var quote in quotes) quote.quoteId: quote,
|
||||||
|
};
|
||||||
|
await box.putAll(quotesMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete quote
|
||||||
|
Future<void> deleteQuote(String quoteId) async {
|
||||||
|
final box = await quotesBox;
|
||||||
|
await box.delete(quoteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear all quotes
|
||||||
|
Future<void> clearQuotes() async {
|
||||||
|
final box = await quotesBox;
|
||||||
|
await box.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get quotes by status
|
||||||
|
Future<List<QuoteModel>> getQuotesByStatus(QuoteStatus status) async {
|
||||||
|
final quotes = await getAllQuotes();
|
||||||
|
return quotes.where((quote) => quote.status == status).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search quotes
|
||||||
|
Future<List<QuoteModel>> searchQuotes(String query) async {
|
||||||
|
final quotes = await getAllQuotes();
|
||||||
|
final lowerQuery = query.toLowerCase();
|
||||||
|
|
||||||
|
return quotes.where((quote) {
|
||||||
|
final matchesNumber = quote.quoteNumber.toLowerCase().contains(lowerQuery);
|
||||||
|
final matchesProject =
|
||||||
|
quote.projectName?.toLowerCase().contains(lowerQuery) ?? false;
|
||||||
|
return matchesNumber || matchesProject;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seed mock data for development
|
||||||
|
Future<void> seedMockQuotes() async {
|
||||||
|
final mockQuotes = [
|
||||||
|
QuoteModel(
|
||||||
|
quoteId: 'quote_001',
|
||||||
|
quoteNumber: 'YC001234',
|
||||||
|
userId: 'user_001',
|
||||||
|
status: QuoteStatus.viewed,
|
||||||
|
totalAmount: 45000000,
|
||||||
|
discountAmount: 2250000,
|
||||||
|
finalAmount: 42750000,
|
||||||
|
projectName: 'Villa Thủ Đức - Giai đoạn 2',
|
||||||
|
notes: 'Khách hàng yêu cầu giảm giá 5%',
|
||||||
|
validUntil: DateTime.now().add(const Duration(days: 30)),
|
||||||
|
createdAt: DateTime(2023, 8, 5),
|
||||||
|
),
|
||||||
|
QuoteModel(
|
||||||
|
quoteId: 'quote_002',
|
||||||
|
quoteNumber: 'YC001233',
|
||||||
|
userId: 'user_001',
|
||||||
|
status: QuoteStatus.accepted,
|
||||||
|
totalAmount: 125500000,
|
||||||
|
discountAmount: 0,
|
||||||
|
finalAmount: 125500000,
|
||||||
|
projectName: 'Chung cư Landmark Center',
|
||||||
|
notes: 'Tổng giá trị: 125.500.000 VND',
|
||||||
|
validUntil: DateTime.now().add(const Duration(days: 45)),
|
||||||
|
createdAt: DateTime(2023, 8, 3),
|
||||||
|
),
|
||||||
|
QuoteModel(
|
||||||
|
quoteId: 'quote_003',
|
||||||
|
quoteNumber: 'YC001232',
|
||||||
|
userId: 'user_001',
|
||||||
|
status: QuoteStatus.converted,
|
||||||
|
totalAmount: 32500000,
|
||||||
|
discountAmount: 500000,
|
||||||
|
finalAmount: 32000000,
|
||||||
|
projectName: 'Nhà phố Bình Thạnh',
|
||||||
|
notes: 'Mã đơn hàng: #DH005432',
|
||||||
|
convertedOrderId: 'order_005432',
|
||||||
|
validUntil: DateTime.now().subtract(const Duration(days: 5)),
|
||||||
|
createdAt: DateTime(2023, 8, 1),
|
||||||
|
),
|
||||||
|
QuoteModel(
|
||||||
|
quoteId: 'quote_004',
|
||||||
|
quoteNumber: 'YC001231',
|
||||||
|
userId: 'user_001',
|
||||||
|
status: QuoteStatus.sent,
|
||||||
|
totalAmount: 78000000,
|
||||||
|
discountAmount: 3000000,
|
||||||
|
finalAmount: 75000000,
|
||||||
|
projectName: 'Văn phòng Quận 7',
|
||||||
|
notes: 'Chờ khách hàng phản hồi',
|
||||||
|
validUntil: DateTime.now().add(const Duration(days: 20)),
|
||||||
|
createdAt: DateTime(2023, 7, 31),
|
||||||
|
),
|
||||||
|
QuoteModel(
|
||||||
|
quoteId: 'quote_005',
|
||||||
|
quoteNumber: 'YC001230',
|
||||||
|
userId: 'user_001',
|
||||||
|
status: QuoteStatus.draft,
|
||||||
|
totalAmount: 185000000,
|
||||||
|
discountAmount: 5000000,
|
||||||
|
finalAmount: 180000000,
|
||||||
|
projectName: 'Resort Vũng Tàu',
|
||||||
|
notes: 'Yêu cầu báo giá cho khu vực pool và spa',
|
||||||
|
validUntil: DateTime.now().add(const Duration(days: 60)),
|
||||||
|
createdAt: DateTime(2023, 7, 29),
|
||||||
|
),
|
||||||
|
QuoteModel(
|
||||||
|
quoteId: 'quote_006',
|
||||||
|
quoteNumber: 'YC001229',
|
||||||
|
userId: 'user_001',
|
||||||
|
status: QuoteStatus.cancelled,
|
||||||
|
totalAmount: 56000000,
|
||||||
|
discountAmount: 1000000,
|
||||||
|
finalAmount: 55000000,
|
||||||
|
projectName: 'Showroom Quận 1',
|
||||||
|
notes: 'Khách hàng hủy dự án',
|
||||||
|
validUntil: DateTime.now().subtract(const Duration(days: 10)),
|
||||||
|
createdAt: DateTime(2023, 7, 25),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
await saveQuotes(mockQuotes);
|
||||||
|
}
|
||||||
|
}
|
||||||
513
lib/features/quotes/presentation/pages/quotes_page.dart
Normal file
513
lib/features/quotes/presentation/pages/quotes_page.dart
Normal file
@@ -0,0 +1,513 @@
|
|||||||
|
/// Page: Quotes Page
|
||||||
|
///
|
||||||
|
/// Displays list of quote requests with search and filter functionality.
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:worker/core/constants/ui_constants.dart';
|
||||||
|
import 'package:worker/core/database/models/enums.dart';
|
||||||
|
import 'package:worker/core/theme/colors.dart';
|
||||||
|
import 'package:worker/features/quotes/data/models/quote_model.dart';
|
||||||
|
import 'package:worker/features/quotes/presentation/providers/quotes_provider.dart';
|
||||||
|
|
||||||
|
/// Quotes Page
|
||||||
|
///
|
||||||
|
/// Features:
|
||||||
|
/// - Search bar for quote numbers and project names
|
||||||
|
/// - Filter section
|
||||||
|
/// - List of quote request cards with status indicators
|
||||||
|
/// - Pull-to-refresh
|
||||||
|
class QuotesPage extends ConsumerStatefulWidget {
|
||||||
|
const QuotesPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<QuotesPage> createState() => _QuotesPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _QuotesPageState extends ConsumerState<QuotesPage> {
|
||||||
|
final TextEditingController _searchController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_searchController.addListener(_onSearchChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_searchController..removeListener(_onSearchChanged)
|
||||||
|
..dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSearchChanged() {
|
||||||
|
ref.read(quoteSearchQueryProvider.notifier).updateQuery(
|
||||||
|
_searchController.text,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final filteredQuotesAsync = ref.watch(filteredQuotesProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF4F6F8),
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back, color: Colors.black),
|
||||||
|
onPressed: () => context.pop(),
|
||||||
|
),
|
||||||
|
title: const Text(
|
||||||
|
'Yêu cầu báo giá',
|
||||||
|
style: TextStyle(color: Colors.black),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.add, color: Colors.black),
|
||||||
|
onPressed: () {
|
||||||
|
// TODO: Navigate to quote create page
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Tạo yêu cầu báo giá mới')),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
elevation: AppBarSpecs.elevation,
|
||||||
|
backgroundColor: AppColors.white,
|
||||||
|
foregroundColor: AppColors.grey900,
|
||||||
|
centerTitle: false,
|
||||||
|
),
|
||||||
|
body: RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
await ref.read(quotesProvider.notifier).refresh();
|
||||||
|
},
|
||||||
|
child: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
// Search Bar and Filter Button
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: _buildSearchAndFilterRow(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Quotes List
|
||||||
|
SliverPadding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 0, 16, 24),
|
||||||
|
sliver: filteredQuotesAsync.when(
|
||||||
|
data: (quotes) {
|
||||||
|
if (quotes.isEmpty) {
|
||||||
|
return _buildEmptyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
return SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) {
|
||||||
|
final quote = quotes[index];
|
||||||
|
return _buildQuoteCard(quote);
|
||||||
|
},
|
||||||
|
childCount: quotes.length,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => _buildLoadingState(),
|
||||||
|
error: (error, stack) => _buildErrorState(error),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build search bar and filter button row
|
||||||
|
Widget _buildSearchAndFilterRow() {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
// Search Bar
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.05),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
controller: _searchController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Tìm theo mã yêu cầu hoặc tên dự án',
|
||||||
|
hintStyle: const TextStyle(
|
||||||
|
color: AppColors.grey500,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
prefixIcon: const Icon(
|
||||||
|
Icons.search,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
suffixIcon: _searchController.text.isNotEmpty
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(Icons.clear, color: AppColors.grey500),
|
||||||
|
onPressed: () {
|
||||||
|
_searchController.clear();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
|
// Filter Button
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.05),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
_showFilterDialog();
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Icons.filter_list,
|
||||||
|
color: AppColors.primaryBlue,
|
||||||
|
),
|
||||||
|
iconSize: 24,
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
minWidth: 48,
|
||||||
|
minHeight: 48,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build quote card
|
||||||
|
Widget _buildQuoteCard(QuoteModel quote) {
|
||||||
|
final dateFormatter = DateFormat('dd/MM/yyyy');
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
|
elevation: 1,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
// TODO: Navigate to quote detail
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Chi tiết báo giá ${quote.quoteNumber}')),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Quote ID and Date
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'#${quote.quoteNumber}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: AppColors.grey900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
dateFormatter.format(quote.createdAt),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// Project Name
|
||||||
|
if (quote.projectName != null)
|
||||||
|
Text(
|
||||||
|
'Dự án: ${quote.projectName}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: AppColors.grey900,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
|
||||||
|
// Additional Info (placeholder for now)
|
||||||
|
const Text(
|
||||||
|
'5 sản phẩm - Diện tích: 200m²',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// Status Badge
|
||||||
|
_buildStatusBadge(quote.status),
|
||||||
|
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// Notes
|
||||||
|
if (quote.notes != null && quote.notes!.isNotEmpty)
|
||||||
|
Text(
|
||||||
|
quote.notes!,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build status badge
|
||||||
|
Widget _buildStatusBadge(QuoteStatus status) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _getStatusColor(status).withValues(alpha: 0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(
|
||||||
|
color: _getStatusColor(status).withValues(alpha: 0.3),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
_getStatusText(status),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: _getStatusColor(status),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get status color
|
||||||
|
Color _getStatusColor(QuoteStatus status) {
|
||||||
|
switch (status) {
|
||||||
|
case QuoteStatus.draft:
|
||||||
|
return AppColors.warning;
|
||||||
|
case QuoteStatus.sent:
|
||||||
|
return AppColors.info;
|
||||||
|
case QuoteStatus.viewed:
|
||||||
|
return const Color(0xFFF59E0B); // yellow/orange
|
||||||
|
case QuoteStatus.accepted:
|
||||||
|
return AppColors.primaryBlue;
|
||||||
|
case QuoteStatus.rejected:
|
||||||
|
return AppColors.danger;
|
||||||
|
case QuoteStatus.expired:
|
||||||
|
return AppColors.grey500;
|
||||||
|
case QuoteStatus.converted:
|
||||||
|
return AppColors.success;
|
||||||
|
case QuoteStatus.cancelled:
|
||||||
|
return AppColors.grey500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get status text
|
||||||
|
String _getStatusText(QuoteStatus status) {
|
||||||
|
switch (status) {
|
||||||
|
case QuoteStatus.draft:
|
||||||
|
return 'Chờ duyệt';
|
||||||
|
case QuoteStatus.sent:
|
||||||
|
return 'Đã gửi';
|
||||||
|
case QuoteStatus.viewed:
|
||||||
|
return 'Đang đàm phán';
|
||||||
|
case QuoteStatus.accepted:
|
||||||
|
return 'Đã chốt';
|
||||||
|
case QuoteStatus.rejected:
|
||||||
|
return 'Từ chối';
|
||||||
|
case QuoteStatus.expired:
|
||||||
|
return 'Hết hạn';
|
||||||
|
case QuoteStatus.converted:
|
||||||
|
return 'Đã thành đơn hàng';
|
||||||
|
case QuoteStatus.cancelled:
|
||||||
|
return 'Đã hủy';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show filter dialog
|
||||||
|
void _showFilterDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
final selectedStatus = ref.read(selectedQuoteStatusProvider);
|
||||||
|
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Lọc theo trạng thái'),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
_buildFilterOption(
|
||||||
|
context,
|
||||||
|
'Tất cả',
|
||||||
|
null,
|
||||||
|
selectedStatus == null,
|
||||||
|
),
|
||||||
|
...QuoteStatus.values.map((status) {
|
||||||
|
return _buildFilterOption(
|
||||||
|
context,
|
||||||
|
_getStatusText(status),
|
||||||
|
status,
|
||||||
|
selectedStatus == status,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text('Đóng'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build filter option
|
||||||
|
Widget _buildFilterOption(
|
||||||
|
BuildContext context,
|
||||||
|
String label,
|
||||||
|
QuoteStatus? status,
|
||||||
|
bool isSelected,
|
||||||
|
) {
|
||||||
|
return RadioListTile<QuoteStatus?>(
|
||||||
|
title: Text(label),
|
||||||
|
value: status,
|
||||||
|
groupValue: ref.watch(selectedQuoteStatusProvider),
|
||||||
|
onChanged: (value) {
|
||||||
|
ref.read(selectedQuoteStatusProvider.notifier).selectStatus(value);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
activeColor: AppColors.primaryBlue,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build empty state
|
||||||
|
Widget _buildEmptyState() {
|
||||||
|
return SliverFillRemaining(
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.receipt_long_outlined,
|
||||||
|
size: 80,
|
||||||
|
color: AppColors.grey500.withValues(alpha: 0.5),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text(
|
||||||
|
'Không có yêu cầu báo giá nào',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
const Text(
|
||||||
|
'Kéo xuống để làm mới',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build loading state
|
||||||
|
Widget _buildLoadingState() {
|
||||||
|
return const SliverFillRemaining(
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build error state
|
||||||
|
Widget _buildErrorState(Object error) {
|
||||||
|
return SliverFillRemaining(
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.error_outline,
|
||||||
|
size: 80,
|
||||||
|
color: AppColors.danger.withValues(alpha: 0.7),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text(
|
||||||
|
'Có lỗi xảy ra',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppColors.grey900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
error.toString(),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
138
lib/features/quotes/presentation/providers/quotes_provider.dart
Normal file
138
lib/features/quotes/presentation/providers/quotes_provider.dart
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/// Providers: Quotes
|
||||||
|
///
|
||||||
|
/// Riverpod providers for managing quotes state.
|
||||||
|
library;
|
||||||
|
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:worker/core/database/models/enums.dart';
|
||||||
|
import 'package:worker/features/quotes/data/datasources/quotes_local_datasource.dart';
|
||||||
|
import 'package:worker/features/quotes/data/models/quote_model.dart';
|
||||||
|
|
||||||
|
part 'quotes_provider.g.dart';
|
||||||
|
|
||||||
|
/// Quotes Local Data Source Provider
|
||||||
|
@riverpod
|
||||||
|
QuotesLocalDataSource quotesLocalDataSource(Ref ref) {
|
||||||
|
return QuotesLocalDataSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Quotes Provider
|
||||||
|
///
|
||||||
|
/// Provides list of all quotes from local data source.
|
||||||
|
@riverpod
|
||||||
|
class Quotes extends _$Quotes {
|
||||||
|
@override
|
||||||
|
Future<List<QuoteModel>> build() async {
|
||||||
|
final datasource = ref.read(quotesLocalDataSourceProvider);
|
||||||
|
|
||||||
|
// Seed mock data on first load
|
||||||
|
final quotes = await datasource.getAllQuotes();
|
||||||
|
if (quotes.isEmpty) {
|
||||||
|
await datasource.seedMockQuotes();
|
||||||
|
return await datasource.getAllQuotes();
|
||||||
|
}
|
||||||
|
|
||||||
|
return quotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refresh quotes
|
||||||
|
Future<void> refresh() async {
|
||||||
|
state = const AsyncValue.loading();
|
||||||
|
state = await AsyncValue.guard(() async {
|
||||||
|
return await ref.read(quotesLocalDataSourceProvider).getAllQuotes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Quote Search Query Provider
|
||||||
|
@riverpod
|
||||||
|
class QuoteSearchQuery extends _$QuoteSearchQuery {
|
||||||
|
@override
|
||||||
|
String build() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateQuery(String query) {
|
||||||
|
state = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearQuery() {
|
||||||
|
state = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Selected Quote Status Filter Provider
|
||||||
|
@riverpod
|
||||||
|
class SelectedQuoteStatus extends _$SelectedQuoteStatus {
|
||||||
|
@override
|
||||||
|
QuoteStatus? build() {
|
||||||
|
return null; // null means show all statuses
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectStatus(QuoteStatus? status) {
|
||||||
|
state = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearFilter() {
|
||||||
|
state = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Filtered Quotes Provider
|
||||||
|
///
|
||||||
|
/// Filters quotes by search query and status.
|
||||||
|
@riverpod
|
||||||
|
Future<List<QuoteModel>> filteredQuotes(Ref ref) async {
|
||||||
|
final quotesAsync = ref.watch(quotesProvider);
|
||||||
|
final searchQuery = ref.watch(quoteSearchQueryProvider);
|
||||||
|
final selectedStatus = ref.watch(selectedQuoteStatusProvider);
|
||||||
|
|
||||||
|
return quotesAsync.when(
|
||||||
|
data: (quotes) {
|
||||||
|
var filtered = quotes;
|
||||||
|
|
||||||
|
// Filter by search query
|
||||||
|
if (searchQuery.isNotEmpty) {
|
||||||
|
final lowerQuery = searchQuery.toLowerCase();
|
||||||
|
filtered = filtered.where((quote) {
|
||||||
|
final matchesNumber = quote.quoteNumber.toLowerCase().contains(lowerQuery);
|
||||||
|
final matchesProject =
|
||||||
|
quote.projectName?.toLowerCase().contains(lowerQuery) ?? false;
|
||||||
|
return matchesNumber || matchesProject;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by status
|
||||||
|
if (selectedStatus != null) {
|
||||||
|
filtered = filtered.where((quote) => quote.status == selectedStatus).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by creation date (newest first)
|
||||||
|
filtered.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
},
|
||||||
|
loading: () => [],
|
||||||
|
error: (error, stack) => [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Quotes Count by Status Provider
|
||||||
|
@riverpod
|
||||||
|
Future<Map<QuoteStatus, int>> quotesCountByStatus(Ref ref) async {
|
||||||
|
final quotesAsync = ref.watch(quotesProvider);
|
||||||
|
|
||||||
|
return quotesAsync.when(
|
||||||
|
data: (quotes) {
|
||||||
|
final counts = <QuoteStatus, int>{};
|
||||||
|
|
||||||
|
for (final status in QuoteStatus.values) {
|
||||||
|
counts[status] = quotes.where((quote) => quote.status == status).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return counts;
|
||||||
|
},
|
||||||
|
loading: () => {},
|
||||||
|
error: (error, stack) => {},
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,338 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'quotes_provider.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint, type=warning
|
||||||
|
/// Quotes Local Data Source Provider
|
||||||
|
|
||||||
|
@ProviderFor(quotesLocalDataSource)
|
||||||
|
const quotesLocalDataSourceProvider = QuotesLocalDataSourceProvider._();
|
||||||
|
|
||||||
|
/// Quotes Local Data Source Provider
|
||||||
|
|
||||||
|
final class QuotesLocalDataSourceProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
QuotesLocalDataSource,
|
||||||
|
QuotesLocalDataSource,
|
||||||
|
QuotesLocalDataSource
|
||||||
|
>
|
||||||
|
with $Provider<QuotesLocalDataSource> {
|
||||||
|
/// Quotes Local Data Source Provider
|
||||||
|
const QuotesLocalDataSourceProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'quotesLocalDataSourceProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$quotesLocalDataSourceHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$ProviderElement<QuotesLocalDataSource> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $ProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
QuotesLocalDataSource create(Ref ref) {
|
||||||
|
return quotesLocalDataSource(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(QuotesLocalDataSource value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<QuotesLocalDataSource>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$quotesLocalDataSourceHash() =>
|
||||||
|
r'02a822db926d8d80460bcc27a08ea494dff6c441';
|
||||||
|
|
||||||
|
/// Quotes Provider
|
||||||
|
///
|
||||||
|
/// Provides list of all quotes from local data source.
|
||||||
|
|
||||||
|
@ProviderFor(Quotes)
|
||||||
|
const quotesProvider = QuotesProvider._();
|
||||||
|
|
||||||
|
/// Quotes Provider
|
||||||
|
///
|
||||||
|
/// Provides list of all quotes from local data source.
|
||||||
|
final class QuotesProvider
|
||||||
|
extends $AsyncNotifierProvider<Quotes, List<QuoteModel>> {
|
||||||
|
/// Quotes Provider
|
||||||
|
///
|
||||||
|
/// Provides list of all quotes from local data source.
|
||||||
|
const QuotesProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'quotesProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$quotesHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
Quotes create() => Quotes();
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$quotesHash() => r'f5011c354218f10da95af16269c956416fca3303';
|
||||||
|
|
||||||
|
/// Quotes Provider
|
||||||
|
///
|
||||||
|
/// Provides list of all quotes from local data source.
|
||||||
|
|
||||||
|
abstract class _$Quotes extends $AsyncNotifier<List<QuoteModel>> {
|
||||||
|
FutureOr<List<QuoteModel>> build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build();
|
||||||
|
final ref =
|
||||||
|
this.ref as $Ref<AsyncValue<List<QuoteModel>>, List<QuoteModel>>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<AsyncValue<List<QuoteModel>>, List<QuoteModel>>,
|
||||||
|
AsyncValue<List<QuoteModel>>,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Quote Search Query Provider
|
||||||
|
|
||||||
|
@ProviderFor(QuoteSearchQuery)
|
||||||
|
const quoteSearchQueryProvider = QuoteSearchQueryProvider._();
|
||||||
|
|
||||||
|
/// Quote Search Query Provider
|
||||||
|
final class QuoteSearchQueryProvider
|
||||||
|
extends $NotifierProvider<QuoteSearchQuery, String> {
|
||||||
|
/// Quote Search Query Provider
|
||||||
|
const QuoteSearchQueryProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'quoteSearchQueryProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$quoteSearchQueryHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
QuoteSearchQuery create() => QuoteSearchQuery();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(String value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<String>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$quoteSearchQueryHash() => r'cc889177beb9905a80b223cba14612bb0c419103';
|
||||||
|
|
||||||
|
/// Quote Search Query Provider
|
||||||
|
|
||||||
|
abstract class _$QuoteSearchQuery extends $Notifier<String> {
|
||||||
|
String build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build();
|
||||||
|
final ref = this.ref as $Ref<String, String>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<String, String>,
|
||||||
|
String,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Selected Quote Status Filter Provider
|
||||||
|
|
||||||
|
@ProviderFor(SelectedQuoteStatus)
|
||||||
|
const selectedQuoteStatusProvider = SelectedQuoteStatusProvider._();
|
||||||
|
|
||||||
|
/// Selected Quote Status Filter Provider
|
||||||
|
final class SelectedQuoteStatusProvider
|
||||||
|
extends $NotifierProvider<SelectedQuoteStatus, QuoteStatus?> {
|
||||||
|
/// Selected Quote Status Filter Provider
|
||||||
|
const SelectedQuoteStatusProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'selectedQuoteStatusProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$selectedQuoteStatusHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
SelectedQuoteStatus create() => SelectedQuoteStatus();
|
||||||
|
|
||||||
|
/// {@macro riverpod.override_with_value}
|
||||||
|
Override overrideWithValue(QuoteStatus? value) {
|
||||||
|
return $ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
providerOverride: $SyncValueProvider<QuoteStatus?>(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$selectedQuoteStatusHash() =>
|
||||||
|
r'5c1ecf114bbb85a00174dad255c939288722658b';
|
||||||
|
|
||||||
|
/// Selected Quote Status Filter Provider
|
||||||
|
|
||||||
|
abstract class _$SelectedQuoteStatus extends $Notifier<QuoteStatus?> {
|
||||||
|
QuoteStatus? build();
|
||||||
|
@$mustCallSuper
|
||||||
|
@override
|
||||||
|
void runBuild() {
|
||||||
|
final created = build();
|
||||||
|
final ref = this.ref as $Ref<QuoteStatus?, QuoteStatus?>;
|
||||||
|
final element =
|
||||||
|
ref.element
|
||||||
|
as $ClassProviderElement<
|
||||||
|
AnyNotifier<QuoteStatus?, QuoteStatus?>,
|
||||||
|
QuoteStatus?,
|
||||||
|
Object?,
|
||||||
|
Object?
|
||||||
|
>;
|
||||||
|
element.handleValue(ref, created);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Filtered Quotes Provider
|
||||||
|
///
|
||||||
|
/// Filters quotes by search query and status.
|
||||||
|
|
||||||
|
@ProviderFor(filteredQuotes)
|
||||||
|
const filteredQuotesProvider = FilteredQuotesProvider._();
|
||||||
|
|
||||||
|
/// Filtered Quotes Provider
|
||||||
|
///
|
||||||
|
/// Filters quotes by search query and status.
|
||||||
|
|
||||||
|
final class FilteredQuotesProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
AsyncValue<List<QuoteModel>>,
|
||||||
|
List<QuoteModel>,
|
||||||
|
FutureOr<List<QuoteModel>>
|
||||||
|
>
|
||||||
|
with $FutureModifier<List<QuoteModel>>, $FutureProvider<List<QuoteModel>> {
|
||||||
|
/// Filtered Quotes Provider
|
||||||
|
///
|
||||||
|
/// Filters quotes by search query and status.
|
||||||
|
const FilteredQuotesProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'filteredQuotesProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$filteredQuotesHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$FutureProviderElement<List<QuoteModel>> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $FutureProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<List<QuoteModel>> create(Ref ref) {
|
||||||
|
return filteredQuotes(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$filteredQuotesHash() => r'77076cfa483cb81cc56972bca6a3c1e97861165c';
|
||||||
|
|
||||||
|
/// Quotes Count by Status Provider
|
||||||
|
|
||||||
|
@ProviderFor(quotesCountByStatus)
|
||||||
|
const quotesCountByStatusProvider = QuotesCountByStatusProvider._();
|
||||||
|
|
||||||
|
/// Quotes Count by Status Provider
|
||||||
|
|
||||||
|
final class QuotesCountByStatusProvider
|
||||||
|
extends
|
||||||
|
$FunctionalProvider<
|
||||||
|
AsyncValue<Map<QuoteStatus, int>>,
|
||||||
|
Map<QuoteStatus, int>,
|
||||||
|
FutureOr<Map<QuoteStatus, int>>
|
||||||
|
>
|
||||||
|
with
|
||||||
|
$FutureModifier<Map<QuoteStatus, int>>,
|
||||||
|
$FutureProvider<Map<QuoteStatus, int>> {
|
||||||
|
/// Quotes Count by Status Provider
|
||||||
|
const QuotesCountByStatusProvider._()
|
||||||
|
: super(
|
||||||
|
from: null,
|
||||||
|
argument: null,
|
||||||
|
retry: null,
|
||||||
|
name: r'quotesCountByStatusProvider',
|
||||||
|
isAutoDispose: true,
|
||||||
|
dependencies: null,
|
||||||
|
$allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String debugGetCreateSourceHash() => _$quotesCountByStatusHash();
|
||||||
|
|
||||||
|
@$internal
|
||||||
|
@override
|
||||||
|
$FutureProviderElement<Map<QuoteStatus, int>> $createElement(
|
||||||
|
$ProviderPointer pointer,
|
||||||
|
) => $FutureProviderElement(pointer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<Map<QuoteStatus, int>> create(Ref ref) {
|
||||||
|
return quotesCountByStatus(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$quotesCountByStatusHash() =>
|
||||||
|
r'474b62ad0ccf890df1c33c64a17f9a0f428f676e';
|
||||||
Reference in New Issue
Block a user