add auth, format
This commit is contained in:
@@ -74,7 +74,9 @@ class QuotesLocalDataSource {
|
||||
final lowerQuery = query.toLowerCase();
|
||||
|
||||
return quotes.where((quote) {
|
||||
final matchesNumber = quote.quoteNumber.toLowerCase().contains(lowerQuery);
|
||||
final matchesNumber = quote.quoteNumber.toLowerCase().contains(
|
||||
lowerQuery,
|
||||
);
|
||||
final matchesProject =
|
||||
quote.projectName?.toLowerCase().contains(lowerQuery) ?? false;
|
||||
return matchesNumber || matchesProject;
|
||||
|
||||
@@ -5,17 +5,36 @@ part 'quote_item_model.g.dart';
|
||||
|
||||
@HiveType(typeId: HiveTypeIds.quoteItemModel)
|
||||
class QuoteItemModel extends HiveObject {
|
||||
QuoteItemModel({required this.quoteItemId, required this.quoteId, required this.productId, required this.quantity, required this.originalPrice, required this.negotiatedPrice, required this.discountPercent, required this.subtotal, this.notes});
|
||||
|
||||
@HiveField(0) final String quoteItemId;
|
||||
@HiveField(1) final String quoteId;
|
||||
@HiveField(2) final String productId;
|
||||
@HiveField(3) final double quantity;
|
||||
@HiveField(4) final double originalPrice;
|
||||
@HiveField(5) final double negotiatedPrice;
|
||||
@HiveField(6) final double discountPercent;
|
||||
@HiveField(7) final double subtotal;
|
||||
@HiveField(8) final String? notes;
|
||||
QuoteItemModel({
|
||||
required this.quoteItemId,
|
||||
required this.quoteId,
|
||||
required this.productId,
|
||||
required this.quantity,
|
||||
required this.originalPrice,
|
||||
required this.negotiatedPrice,
|
||||
required this.discountPercent,
|
||||
required this.subtotal,
|
||||
this.notes,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
final String quoteItemId;
|
||||
@HiveField(1)
|
||||
final String quoteId;
|
||||
@HiveField(2)
|
||||
final String productId;
|
||||
@HiveField(3)
|
||||
final double quantity;
|
||||
@HiveField(4)
|
||||
final double originalPrice;
|
||||
@HiveField(5)
|
||||
final double negotiatedPrice;
|
||||
@HiveField(6)
|
||||
final double discountPercent;
|
||||
@HiveField(7)
|
||||
final double subtotal;
|
||||
@HiveField(8)
|
||||
final String? notes;
|
||||
|
||||
factory QuoteItemModel.fromJson(Map<String, dynamic> json) => QuoteItemModel(
|
||||
quoteItemId: json['quote_item_id'] as String,
|
||||
|
||||
@@ -7,24 +7,57 @@ part 'quote_model.g.dart';
|
||||
|
||||
@HiveType(typeId: HiveTypeIds.quoteModel)
|
||||
class QuoteModel extends HiveObject {
|
||||
QuoteModel({required this.quoteId, required this.quoteNumber, required this.userId, required this.status, required this.totalAmount, required this.discountAmount, required this.finalAmount, this.projectName, this.deliveryAddress, this.paymentTerms, this.notes, this.validUntil, this.convertedOrderId, this.erpnextQuotation, required this.createdAt, this.updatedAt});
|
||||
|
||||
@HiveField(0) final String quoteId;
|
||||
@HiveField(1) final String quoteNumber;
|
||||
@HiveField(2) final String userId;
|
||||
@HiveField(3) final QuoteStatus status;
|
||||
@HiveField(4) final double totalAmount;
|
||||
@HiveField(5) final double discountAmount;
|
||||
@HiveField(6) final double finalAmount;
|
||||
@HiveField(7) final String? projectName;
|
||||
@HiveField(8) final String? deliveryAddress;
|
||||
@HiveField(9) final String? paymentTerms;
|
||||
@HiveField(10) final String? notes;
|
||||
@HiveField(11) final DateTime? validUntil;
|
||||
@HiveField(12) final String? convertedOrderId;
|
||||
@HiveField(13) final String? erpnextQuotation;
|
||||
@HiveField(14) final DateTime createdAt;
|
||||
@HiveField(15) final DateTime? updatedAt;
|
||||
QuoteModel({
|
||||
required this.quoteId,
|
||||
required this.quoteNumber,
|
||||
required this.userId,
|
||||
required this.status,
|
||||
required this.totalAmount,
|
||||
required this.discountAmount,
|
||||
required this.finalAmount,
|
||||
this.projectName,
|
||||
this.deliveryAddress,
|
||||
this.paymentTerms,
|
||||
this.notes,
|
||||
this.validUntil,
|
||||
this.convertedOrderId,
|
||||
this.erpnextQuotation,
|
||||
required this.createdAt,
|
||||
this.updatedAt,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
final String quoteId;
|
||||
@HiveField(1)
|
||||
final String quoteNumber;
|
||||
@HiveField(2)
|
||||
final String userId;
|
||||
@HiveField(3)
|
||||
final QuoteStatus status;
|
||||
@HiveField(4)
|
||||
final double totalAmount;
|
||||
@HiveField(5)
|
||||
final double discountAmount;
|
||||
@HiveField(6)
|
||||
final double finalAmount;
|
||||
@HiveField(7)
|
||||
final String? projectName;
|
||||
@HiveField(8)
|
||||
final String? deliveryAddress;
|
||||
@HiveField(9)
|
||||
final String? paymentTerms;
|
||||
@HiveField(10)
|
||||
final String? notes;
|
||||
@HiveField(11)
|
||||
final DateTime? validUntil;
|
||||
@HiveField(12)
|
||||
final String? convertedOrderId;
|
||||
@HiveField(13)
|
||||
final String? erpnextQuotation;
|
||||
@HiveField(14)
|
||||
final DateTime createdAt;
|
||||
@HiveField(15)
|
||||
final DateTime? updatedAt;
|
||||
|
||||
factory QuoteModel.fromJson(Map<String, dynamic> json) => QuoteModel(
|
||||
quoteId: json['quote_id'] as String,
|
||||
@@ -35,14 +68,20 @@ class QuoteModel extends HiveObject {
|
||||
discountAmount: (json['discount_amount'] as num).toDouble(),
|
||||
finalAmount: (json['final_amount'] as num).toDouble(),
|
||||
projectName: json['project_name'] as String?,
|
||||
deliveryAddress: json['delivery_address'] != null ? jsonEncode(json['delivery_address']) : null,
|
||||
deliveryAddress: json['delivery_address'] != null
|
||||
? jsonEncode(json['delivery_address'])
|
||||
: null,
|
||||
paymentTerms: json['payment_terms'] as String?,
|
||||
notes: json['notes'] as String?,
|
||||
validUntil: json['valid_until'] != null ? DateTime.parse(json['valid_until']?.toString() ?? '') : null,
|
||||
validUntil: json['valid_until'] != null
|
||||
? DateTime.parse(json['valid_until']?.toString() ?? '')
|
||||
: null,
|
||||
convertedOrderId: json['converted_order_id'] as String?,
|
||||
erpnextQuotation: json['erpnext_quotation'] as String?,
|
||||
createdAt: DateTime.parse(json['created_at']?.toString() ?? ''),
|
||||
updatedAt: json['updated_at'] != null ? DateTime.parse(json['updated_at']?.toString() ?? '') : null,
|
||||
updatedAt: json['updated_at'] != null
|
||||
? DateTime.parse(json['updated_at']?.toString() ?? '')
|
||||
: null,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@@ -54,7 +93,9 @@ class QuoteModel extends HiveObject {
|
||||
'discount_amount': discountAmount,
|
||||
'final_amount': finalAmount,
|
||||
'project_name': projectName,
|
||||
'delivery_address': deliveryAddress != null ? jsonDecode(deliveryAddress!) : null,
|
||||
'delivery_address': deliveryAddress != null
|
||||
? jsonDecode(deliveryAddress!)
|
||||
: null,
|
||||
'payment_terms': paymentTerms,
|
||||
'notes': notes,
|
||||
'valid_until': validUntil?.toIso8601String(),
|
||||
@@ -64,6 +105,7 @@ class QuoteModel extends HiveObject {
|
||||
'updated_at': updatedAt?.toIso8601String(),
|
||||
};
|
||||
|
||||
bool get isExpired => validUntil != null && DateTime.now().isAfter(validUntil!);
|
||||
bool get isExpired =>
|
||||
validUntil != null && DateTime.now().isAfter(validUntil!);
|
||||
bool get isConverted => convertedOrderId != null;
|
||||
}
|
||||
|
||||
@@ -38,15 +38,16 @@ class _QuotesPageState extends ConsumerState<QuotesPage> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController..removeListener(_onSearchChanged)
|
||||
..dispose();
|
||||
_searchController
|
||||
..removeListener(_onSearchChanged)
|
||||
..dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onSearchChanged() {
|
||||
ref.read(quoteSearchQueryProvider.notifier).updateQuery(
|
||||
_searchController.text,
|
||||
);
|
||||
ref
|
||||
.read(quoteSearchQueryProvider.notifier)
|
||||
.updateQuery(_searchController.text);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -104,13 +105,10 @@ class _QuotesPageState extends ConsumerState<QuotesPage> {
|
||||
}
|
||||
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
final quote = quotes[index];
|
||||
return _buildQuoteCard(quote);
|
||||
},
|
||||
childCount: quotes.length,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
final quote = quotes[index];
|
||||
return _buildQuoteCard(quote);
|
||||
}, childCount: quotes.length),
|
||||
);
|
||||
},
|
||||
loading: () => _buildLoadingState(),
|
||||
@@ -149,10 +147,7 @@ class _QuotesPageState extends ConsumerState<QuotesPage> {
|
||||
color: AppColors.grey500,
|
||||
fontSize: 14,
|
||||
),
|
||||
prefixIcon: const Icon(
|
||||
Icons.search,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
prefixIcon: const Icon(Icons.search, color: AppColors.grey500),
|
||||
suffixIcon: _searchController.text.isNotEmpty
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.clear, color: AppColors.grey500),
|
||||
@@ -190,16 +185,10 @@ class _QuotesPageState extends ConsumerState<QuotesPage> {
|
||||
onPressed: () {
|
||||
_showFilterDialog();
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.filter_list,
|
||||
color: AppColors.primaryBlue,
|
||||
),
|
||||
icon: Icon(Icons.filter_list, color: AppColors.primaryBlue),
|
||||
iconSize: 24,
|
||||
padding: const EdgeInsets.all(12),
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 48,
|
||||
minHeight: 48,
|
||||
),
|
||||
constraints: const BoxConstraints(minWidth: 48, minHeight: 48),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -213,9 +202,7 @@ class _QuotesPageState extends ConsumerState<QuotesPage> {
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
@@ -270,10 +257,7 @@ class _QuotesPageState extends ConsumerState<QuotesPage> {
|
||||
// Additional Info (placeholder for now)
|
||||
const Text(
|
||||
'5 sản phẩm - Diện tích: 200m²',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
style: TextStyle(fontSize: 13, color: AppColors.grey500),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
@@ -455,10 +439,7 @@ class _QuotesPageState extends ConsumerState<QuotesPage> {
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'Kéo xuống để làm mới',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
style: TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -469,9 +450,7 @@ class _QuotesPageState extends ConsumerState<QuotesPage> {
|
||||
/// Build loading state
|
||||
Widget _buildLoadingState() {
|
||||
return const SliverFillRemaining(
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -499,10 +478,7 @@ class _QuotesPageState extends ConsumerState<QuotesPage> {
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
error.toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -95,7 +95,9 @@ Future<List<QuoteModel>> filteredQuotes(Ref ref) async {
|
||||
if (searchQuery.isNotEmpty) {
|
||||
final lowerQuery = searchQuery.toLowerCase();
|
||||
filtered = filtered.where((quote) {
|
||||
final matchesNumber = quote.quoteNumber.toLowerCase().contains(lowerQuery);
|
||||
final matchesNumber = quote.quoteNumber.toLowerCase().contains(
|
||||
lowerQuery,
|
||||
);
|
||||
final matchesProject =
|
||||
quote.projectName?.toLowerCase().contains(lowerQuery) ?? false;
|
||||
return matchesNumber || matchesProject;
|
||||
@@ -104,7 +106,9 @@ Future<List<QuoteModel>> filteredQuotes(Ref ref) async {
|
||||
|
||||
// Filter by status
|
||||
if (selectedStatus != null) {
|
||||
filtered = filtered.where((quote) => quote.status == selectedStatus).toList();
|
||||
filtered = filtered
|
||||
.where((quote) => quote.status == selectedStatus)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Sort by creation date (newest first)
|
||||
|
||||
Reference in New Issue
Block a user