update order detail
This commit is contained in:
@@ -16,14 +16,16 @@
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</a>
|
||||
<h1 class="header-title">Chi tiết đơn hàng</h1>
|
||||
<div class="header-actions">
|
||||
<div style="width: 32px;"></div>
|
||||
|
||||
<!--<div class="header-actions">
|
||||
<button class="header-action-btn" onclick="shareOrder()">
|
||||
<i class="fas fa-share"></i>
|
||||
</button>
|
||||
<button class="header-action-btn" onclick="printOrder()">
|
||||
<i class="fas fa-print"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>-->
|
||||
</div>
|
||||
|
||||
<div class="order-detail-content" style="padding-bottom: 0px;">
|
||||
@@ -46,22 +48,33 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item completed">
|
||||
<div class="timeline-icon">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-title">Xác nhận đơn hàng</div>
|
||||
<div class="timeline-date">03/08/2023 - 10:15</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item active">
|
||||
<div class="timeline-icon">
|
||||
<i class="fas fa-cog fa-spin"></i>
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-title">Đã xác nhận đơn hàng</div>
|
||||
<div class="timeline-date">03/08/2023 - 10:15 (Đang xử lý)</div>
|
||||
<div class="timeline-title">Xử lý</div>
|
||||
<div class="timeline-date">Chuẩn bị hàng và vận chuyển</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-item pending">
|
||||
<div class="timeline-icon">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<i class="fas fa-box-open"></i>
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="timeline-title">Đã hoàn thành</div>
|
||||
<div class="timeline-title">Hoàn thành</div>
|
||||
<div class="timeline-date">Dự kiến: 07/08/2023</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -69,11 +82,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Delivery Information Card -->
|
||||
<div class="delivery-info-card">
|
||||
<!--<div class="delivery-info-card">
|
||||
<h3><i class="fas fa-shipping-fast"></i> Thông tin giao hàng</h3>
|
||||
|
||||
<div class="delivery-details">
|
||||
<!--<div class="delivery-method">
|
||||
<div class="delivery-method">
|
||||
<div class="delivery-method-icon">
|
||||
<i class="fas fa-truck"></i>
|
||||
</div>
|
||||
@@ -81,16 +94,16 @@
|
||||
<div class="method-name">Giao hàng tiêu chuẩn</div>
|
||||
<div class="method-description">Giao trong 3-5 ngày làm việc</div>
|
||||
</div>
|
||||
</div>-->
|
||||
</div>
|
||||
|
||||
<div class="delivery-dates">
|
||||
<!--<div class="date-item">
|
||||
<div class="date-item">
|
||||
<div class="date-label">
|
||||
<i class="fas fa-calendar-alt"></i>
|
||||
Ngày xuất kho
|
||||
</div>
|
||||
<div class="date-value confirmed">05/08/2023</div>
|
||||
</div>-->
|
||||
</div>
|
||||
|
||||
<div class="date-item">
|
||||
<div class="date-label">
|
||||
@@ -120,55 +133,179 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>-->
|
||||
|
||||
<!-- Customer Information -->
|
||||
<div class="customer-info-card">
|
||||
<h3><i class="fas fa-user-circle"></i> Thông tin khách hàng</h3>
|
||||
<div class="customer-details">
|
||||
<div class="customer-row">
|
||||
<span class="customer-label">Tên khách hàng:</span>
|
||||
<span class="customer-value">Nguyễn Văn A</span>
|
||||
<!--<div class="customer-info-card">
|
||||
<h3><i class="fas fa-user-circle"></i> Thông tin khách hàng</h3>-->
|
||||
<div class="delivery-info-card">
|
||||
<h3><i class="fas fa-shipping-fast"></i> Thông tin giao hàng</h3>
|
||||
<!-- Address Section -->
|
||||
<!--<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Địa chỉ nhận hàng
|
||||
</label>
|
||||
<a href="addresses.html" class="block border border-gray-200 rounded-lg p-3 hover:border-blue-500 hover:bg-blue-50 transition group">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="font-semibold text-gray-900 mb-1">Hoàng Minh Hiệp</div>
|
||||
<div class="text-sm text-gray-600 mb-1">0347302911</div>
|
||||
<div class="text-sm text-gray-600">
|
||||
123 Đường Võ Văn Ngân, Phường Linh Chiểu,
|
||||
Thành phố Thủ Đức, TP.HCM
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>-->
|
||||
|
||||
|
||||
<!-- Address Section -->
|
||||
<div class="mb-4">
|
||||
|
||||
<!-- Label + Button -->
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<label class="block text-sm font-medium text-gray-700">
|
||||
Địa chỉ nhận hàng
|
||||
</label>
|
||||
|
||||
<a href="addresses.html"
|
||||
class="text-blue-600 text-sm font-medium hover:underline px-3 py-1 border rounded-lg hover:bg-blue-50">
|
||||
Cập nhật
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Address Box -->
|
||||
<a href="addresses.html"
|
||||
class="block border border-gray-200 rounded-lg p-3 hover:border-blue-500 hover:bg-blue-50 transition group">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="font-semibold text-gray-900 mb-1">Hoàng Minh Hiệp</div>
|
||||
<div class="text-sm text-gray-600 mb-1">0347302911</div>
|
||||
<div class="text-sm text-gray-600">
|
||||
123 Đường Võ Văn Ngân, Phường Linh Chiểu,
|
||||
Thành phố Thủ Đức, TP.HCM
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!--<div class="customer-row">
|
||||
<span class="customer-label">Ngày lấy hàng:</span>
|
||||
<span class="customer-value">07/08/2025</span>
|
||||
</div>
|
||||
<div class="customer-row">
|
||||
<span class="customer-label">Số điện thoại:</span>
|
||||
<span class="customer-value">0901234567</span>
|
||||
</div>
|
||||
<div class="customer-row">
|
||||
<span class="customer-label">Email:</span>
|
||||
<span class="customer-value">nguyenvana@email.com</span>
|
||||
<span class="customer-label">Ghi chú:</span>
|
||||
<span class="customer-value">Giao hàng trong giờ hành chính. Vui lòng gọi trước 30 phút khi đến giao hàng.</span>
|
||||
</div>
|
||||
<div class="customer-row">
|
||||
<span class="customer-label">Loại khách hàng:</span>
|
||||
<span class="customer-badge vip">DIAMOND</span>
|
||||
</div>-->
|
||||
<!-- Pickup Date -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Ngày lấy hàng
|
||||
</label>
|
||||
<div class="font-semibold text-gray-900 mb-1" style="font-weight: 450;">07/08/2025</div>
|
||||
|
||||
|
||||
</div>
|
||||
<!-- Pickup Date -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
Ghi chú
|
||||
</label>
|
||||
<div class="font-semibold text-gray-900 mb-1" style="font-weight: 450;">Giao hàng trong giờ hành chính. Vui lòng gọi trước 30 phút khi đến giao hàng</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Invoice Information -->
|
||||
<div class="customer-info-card">
|
||||
<h3><i class="fas fa-file-invoice"></i> Thông tin hóa đơn</h3>
|
||||
<div class="customer-details">
|
||||
<!-- Title + Update Button -->
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h3 class="flex items-center gap-2" style=" margin-bottom: 0px;">
|
||||
<i class="fas fa-file-invoice"></i>
|
||||
Thông tin hóa đơn
|
||||
</h3>
|
||||
|
||||
<a href="addresses.html"
|
||||
class="text-blue-600 text-sm font-medium hover:underline px-3 py-1 border rounded-lg hover:bg-blue-50">
|
||||
Cập nhật
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-gray-200 pt-4">
|
||||
<a href="addresses.html" class="block border border-gray-200 rounded-lg p-3 hover:border-blue-500 hover:bg-blue-50 transition group">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="font-semibold text-gray-900 mb-1">Công ty TNHH Xây dựng Minh Long</div>
|
||||
<div class="text-sm text-gray-600 mb-0.5">Mã số thuế: 0134000687</div>
|
||||
<div class="text-sm text-gray-600 mb-0.5">Số điện thoại: 0339797979</div>
|
||||
<div class="text-sm text-gray-600 mb-0.5">Email: minhlong.org@gmail.com</div>
|
||||
<div class="text-sm text-gray-600">
|
||||
Địa chỉ: 11 Đường Hoàng Hữu Nam, Phường Linh Chiểu,
|
||||
Thành phố Thủ Đức, TP.HCM
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!--<div class="customer-details">
|
||||
<div class="customer-row">
|
||||
<span class="customer-label">Tên công ty:</span>
|
||||
<span class="customer-value">Công ty TNHH Xây dựng ABC</span>
|
||||
<span class="customer-value">Công ty TNHH Xây dựng Minh Long</span>
|
||||
</div>
|
||||
<div class="customer-row">
|
||||
<span class="customer-label">Mã số thuế:</span>
|
||||
<span class="customer-value">0123456789</span>
|
||||
<span class="customer-value">0134000687</span>
|
||||
</div>
|
||||
<div class="customer-row">
|
||||
<span class="customer-label">Địa chỉ công ty:</span>
|
||||
<span class="customer-value">123 Nguyễn Trãi, Quận 1, TP.HCM</span>
|
||||
<span class="customer-label">Địa chỉ:</span>
|
||||
<span class="customer-value">11 Đường Hoàng Hữu Nam, Phường Linh Chiểu, Thành phố Thủ Đức, TP.HCM</span>
|
||||
</div>
|
||||
<div class="customer-row">
|
||||
<span class="customer-label">Email nhận hóa đơn:</span>
|
||||
<span class="customer-value">ketoan@abc.com</span>
|
||||
<span class="customer-value">minhlong.org@gmail.com</span>
|
||||
</div>
|
||||
<div class="customer-row">
|
||||
<span class="customer-label">Số điện thoại:</span>
|
||||
<span class="customer-value">0339797979</span>
|
||||
</div>
|
||||
<div class="customer-row">
|
||||
<span class="customer-label">Loại hóa đơn:</span>
|
||||
<span class="customer-badge" style="background: #d1ecf1; color: #0c5460;">Hóa đơn VAT</span>
|
||||
</div>
|
||||
</div>-->
|
||||
</div>
|
||||
<!-- Invoices List Block (NEW) -->
|
||||
<div class="delivery-info-card">
|
||||
<h3><i class="fas fa-file-invoice-dollar text-blue-600"></i> Hóa đơn đã xuất</h3>
|
||||
|
||||
<div class="invoices-list">
|
||||
<!-- Invoice Card 1 -->
|
||||
<div class="invoice-item" onclick="window.location.href='invoice-detail.html?id=INV20240001'">
|
||||
<div class="invoice-item-icon">
|
||||
<i class="fas fa-file-invoice"></i>
|
||||
</div>
|
||||
<div class="invoice-item-content">
|
||||
<div class="invoice-item-title">#INV20240001</div>
|
||||
<div class="invoice-item-subtitle">Ngày xuất: 03/08/2024 - 10:00</div>
|
||||
</div>
|
||||
<div class="invoice-item-amount">12.771.000đ</div>
|
||||
<i class="fas fa-chevron-right invoice-item-arrow"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -249,10 +386,11 @@
|
||||
<i class="fas fa-credit-card"></i>
|
||||
Phương thức thanh toán:
|
||||
</div>
|
||||
<div class="payment-value">Chuyển khoản ngân hàng</div>
|
||||
<div class="payment-value">Thanh toán một phần</div>
|
||||
</div>
|
||||
|
||||
<div class="order-notes">
|
||||
|
||||
<!--<div class="order-notes">
|
||||
<div class="notes-label">
|
||||
<i class="fas fa-sticky-note"></i>
|
||||
Ghi chú đơn hàng:
|
||||
@@ -260,9 +398,71 @@
|
||||
<div class="notes-content">
|
||||
Giao hàng trong giờ hành chính. Vui lòng gọi trước 30 phút khi đến giao hàng.
|
||||
</div>
|
||||
</div>-->
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Payment History -->
|
||||
<div class="detail-container">
|
||||
<div class="detail-card">
|
||||
<h3 class="section-title">
|
||||
<i class="fas fa-history" style="color: #2563eb;"></i>
|
||||
Lịch sử thanh toán
|
||||
</h3>
|
||||
|
||||
<div class="payment-history" style ="margin-bottom: 0px;" id="payment-history">
|
||||
<!-- Payment Card 1 (Clickable for modal) -->
|
||||
<div class="history-item" onclick="openPaymentModal('PAY20240001', '6.385.500đ', 'Chuyển khoản', '03/08/2024 - 14:30', 'TK20241020001', 'https://placehold.co/600x400/E8F4FD/005B9A/png?text=Bi%C3%AAn+lai+thanh+to%C3%A1n')">
|
||||
<div class="history-icon">
|
||||
<i class="fas fa-check"></i>
|
||||
</div>
|
||||
<div class="history-content">
|
||||
<div class="history-title">#PAY20240001</div>
|
||||
<!--<div class="history-details">Chuyển khoản | Ref: TK20241020001</div>-->
|
||||
<div class="history-date">03/08/2024 - 14:30</div>
|
||||
</div>
|
||||
<div class="history-amount">6.385.500đ</div>
|
||||
<i class="fas fa-chevron-right" style="color: #9ca3af; margin-left: 8px;"></i>
|
||||
</div>
|
||||
|
||||
<!-- Payment Card 2 -->
|
||||
<div class="history-item" onclick="openPaymentModal('PAY20240002', '6.385.500đ', 'Tiền mặt', '05/08/2024 - 09:15', 'CASH-20240805-001', '')">
|
||||
<div class="history-icon">
|
||||
<i class="fas fa-check"></i>
|
||||
</div>
|
||||
<div class="history-content">
|
||||
<div class="history-title">#PAY20240002</div>
|
||||
<!--<div class="history-details">Tiền mặt | Ref: CASH-20240805-001</div>-->
|
||||
<div class="history-date">05/08/2024 - 09:15</div>
|
||||
</div>
|
||||
<div class="history-amount">6.385.500đ</div>
|
||||
<i class="fas fa-chevron-right" style="color: #9ca3af; margin-left: 8px;"></i>
|
||||
</div>
|
||||
<!-- Payment Summary -->
|
||||
<div class="summary-row">
|
||||
<span>Còn lại:</span>
|
||||
<span class="remaining-amount" id="remaining-amount">10.000.000đ</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-primary" onclick="makePayment()" id="pay-button">
|
||||
<i class="fas fa-credit-card"></i>
|
||||
Thanh toán
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="contactSupport()">
|
||||
<i class="fas fa-comments"></i>
|
||||
Liên hệ hỗ trợ
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<!--<div class="order-actions">
|
||||
@@ -276,11 +476,11 @@
|
||||
</button>
|
||||
</div>-->
|
||||
<!-- Floating Action Button -->
|
||||
<a href="chat-list.html" class="fab-link">
|
||||
<!--<a href="chat-list.html" class="fab-link">
|
||||
<button class="fab">
|
||||
<i class="fas fa-comments"></i>
|
||||
</button>
|
||||
</a>
|
||||
</a>-->
|
||||
<!--<a href="chat-list.html" class="fab">-->
|
||||
<!--<button class="fab">-->
|
||||
<!--<i class="fas fa-comments"></i>-->
|
||||
@@ -683,6 +883,14 @@
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Action Buttons */
|
||||
.order-actions {
|
||||
position: fixed;
|
||||
@@ -731,6 +939,200 @@
|
||||
}
|
||||
|
||||
/* Mobile Responsiveness */
|
||||
/* Invoices List Styles */
|
||||
.invoices-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.invoice-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.invoice-item:hover {
|
||||
border-color: var(--primary-blue);
|
||||
background: #F0F7FF;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.15);
|
||||
}
|
||||
|
||||
.invoice-item-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.invoice-item-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.invoice-item-title {
|
||||
font-weight: 600;
|
||||
color: var(--text-dark);
|
||||
font-size: 14px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.invoice-item-subtitle {
|
||||
font-size: 12px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.invoice-item-amount {
|
||||
font-weight: 700;
|
||||
color: #dc2626;
|
||||
font-size: 14px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.invoice-item-arrow {
|
||||
color: var(--text-light);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.history-item {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.history-item:hover {
|
||||
background: #f8fafc;
|
||||
border-radius: 8px;
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
/* Payment Modal Styles */
|
||||
.payment-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.payment-modal.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.payment-modal-content {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
animation: slideUp 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.payment-modal-header {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.payment-modal-header h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.payment-modal-close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: #f3f4f6;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.payment-modal-close:hover {
|
||||
background: #e5e7eb;
|
||||
}
|
||||
|
||||
.payment-modal-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.payment-detail-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
}
|
||||
|
||||
.payment-detail-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.payment-detail-label {
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.payment-detail-value {
|
||||
color: #1f2937;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.payment-detail-value.amount {
|
||||
color: #065f46;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.payment-receipt-image {
|
||||
margin-top: 20px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.payment-receipt-image img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.status-timeline-card,
|
||||
.delivery-info-card,
|
||||
@@ -763,7 +1165,7 @@
|
||||
.date-item,
|
||||
.customer-row,
|
||||
.summary-row {
|
||||
flex-direction: column;
|
||||
/*flex-direction: column;*/
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
@@ -775,10 +1177,393 @@
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.payment-modal-content {
|
||||
margin: 20px;
|
||||
max-height: calc(100vh - 40px);
|
||||
}
|
||||
|
||||
.invoice-item-amount {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.detail-container {
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.invoice-header {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.invoice-id {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.invoice-date {
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-overdue {
|
||||
background: #fee2e2;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.status-unpaid {
|
||||
background: #fef3c7;
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.status-paid {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.status-partial {
|
||||
background: #e0e7ff;
|
||||
color: #3730a3;
|
||||
}
|
||||
|
||||
.payment-summary {
|
||||
background: #f8fafc;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.summary-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.summary-row:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-top: 12px;
|
||||
border-top: 2px solid #e5e7eb;
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.remaining-amount {
|
||||
color: #dc2626;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.product-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: #f3f4f6;
|
||||
border-radius: 8px;
|
||||
margin-right: 16px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.product-sku {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.product-details {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.product-quantity {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.payment-history {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.history-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.history-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.history-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.history-title {
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.history-details {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.history-date {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.history-amount {
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
color: #065f46;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
padding: 14px 20px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.4);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: #374151;
|
||||
border: 2px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
border-color: #2563eb;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.btn-download {
|
||||
background: #f3f4f6;
|
||||
color: #374151;
|
||||
border: 1px solid #d1d5db;
|
||||
}
|
||||
|
||||
.btn-download:hover {
|
||||
background: #e5e7eb;
|
||||
}
|
||||
|
||||
.download-section {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-history {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.empty-history i {
|
||||
font-size: 32px;
|
||||
margin-bottom: 12px;
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.detail-container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.product-details {
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.summary-row {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.summary-row:last-child {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Payment Detail Modal -->
|
||||
<div id="paymentModal" class="payment-modal">
|
||||
<div class="payment-modal-content">
|
||||
<div class="payment-modal-header">
|
||||
<h3>Chi tiết thanh toán</h3>
|
||||
<button class="payment-modal-close" onclick="closePaymentModal()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="payment-modal-body">
|
||||
<div class="payment-detail-row">
|
||||
<span class="payment-detail-label">Mã giao dịch:</span>
|
||||
<span class="payment-detail-value" id="modal-transaction-id"></span>
|
||||
</div>
|
||||
<div class="payment-detail-row">
|
||||
<span class="payment-detail-label">Thời gian:</span>
|
||||
<span class="payment-detail-value" id="modal-datetime"></span>
|
||||
</div>
|
||||
<div class="payment-detail-row">
|
||||
<span class="payment-detail-label">Phương thức:</span>
|
||||
<span class="payment-detail-value" id="modal-method"></span>
|
||||
</div>
|
||||
<div class="payment-detail-row">
|
||||
<span class="payment-detail-label">Mã tham chiếu:</span>
|
||||
<span class="payment-detail-value" id="modal-reference"></span>
|
||||
</div>
|
||||
<div class="payment-detail-row">
|
||||
<span class="payment-detail-label">Số tiền:</span>
|
||||
<span class="payment-detail-value amount" id="modal-amount"></span>
|
||||
</div>
|
||||
<div id="modal-receipt-container" class="payment-receipt-image" style="display: none;">
|
||||
<img id="modal-receipt-image" src="" alt="Biên lai thanh toán">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openPaymentModal(transactionId, amount, method, datetime, reference, receiptImage) {
|
||||
document.getElementById('modal-transaction-id').textContent = transactionId;
|
||||
document.getElementById('modal-amount').textContent = amount;
|
||||
document.getElementById('modal-method').textContent = method;
|
||||
document.getElementById('modal-datetime').textContent = datetime;
|
||||
document.getElementById('modal-reference').textContent = reference;
|
||||
|
||||
const receiptContainer = document.getElementById('modal-receipt-container');
|
||||
const receiptImg = document.getElementById('modal-receipt-image');
|
||||
|
||||
if (receiptImage && receiptImage !== '') {
|
||||
receiptImg.src = receiptImage;
|
||||
receiptContainer.style.display = 'block';
|
||||
} else {
|
||||
receiptContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
document.getElementById('paymentModal').classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closePaymentModal() {
|
||||
document.getElementById('paymentModal').classList.remove('active');
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
|
||||
// Close modal when clicking outside
|
||||
document.getElementById('paymentModal')?.addEventListener('click', function(e) {
|
||||
if (e.target === this) {
|
||||
closePaymentModal();
|
||||
}
|
||||
});
|
||||
|
||||
function shareOrder() {
|
||||
if (navigator.share) {
|
||||
navigator.share({
|
||||
|
||||
@@ -221,14 +221,14 @@ class AppTheme {
|
||||
|
||||
// ==================== Switch Theme ====================
|
||||
switchTheme: SwitchThemeData(
|
||||
thumbColor: MaterialStateProperty.resolveWith((states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
thumbColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return AppColors.primaryBlue;
|
||||
}
|
||||
return AppColors.grey500;
|
||||
}),
|
||||
trackColor: MaterialStateProperty.resolveWith((states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
trackColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return AppColors.lightBlue;
|
||||
}
|
||||
return AppColors.grey100;
|
||||
@@ -237,20 +237,20 @@ class AppTheme {
|
||||
|
||||
// ==================== Checkbox Theme ====================
|
||||
checkboxTheme: CheckboxThemeData(
|
||||
fillColor: MaterialStateProperty.resolveWith((states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
fillColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return AppColors.primaryBlue;
|
||||
}
|
||||
return AppColors.white;
|
||||
}),
|
||||
checkColor: MaterialStateProperty.all(AppColors.white),
|
||||
checkColor: WidgetStateProperty.all(AppColors.white),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
|
||||
),
|
||||
|
||||
// ==================== Radio Theme ====================
|
||||
radioTheme: RadioThemeData(
|
||||
fillColor: MaterialStateProperty.resolveWith((states) {
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
fillColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.selected)) {
|
||||
return AppColors.primaryBlue;
|
||||
}
|
||||
return AppColors.grey500;
|
||||
|
||||
@@ -7,6 +7,8 @@ library;
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
// ============================================================================
|
||||
// String Extensions
|
||||
@@ -422,26 +424,26 @@ extension BuildContextExtensions on BuildContext {
|
||||
}
|
||||
|
||||
/// Navigate to route
|
||||
Future<T?> push<T>(Widget page) {
|
||||
return Navigator.of(this).push<T>(MaterialPageRoute(builder: (_) => page));
|
||||
}
|
||||
// Future<T?> push<T>(Widget page) {
|
||||
// return Navigator.of(this).push<T>(MaterialPageRoute(builder: (_) => page));
|
||||
// }
|
||||
|
||||
/// Navigate and replace current route
|
||||
Future<T?> pushReplacement<T>(Widget page) {
|
||||
return Navigator.of(
|
||||
this,
|
||||
).pushReplacement<T, void>(MaterialPageRoute(builder: (_) => page));
|
||||
}
|
||||
// Future<T?> pushReplacement<T>(Widget page) {
|
||||
// return Navigator.of(
|
||||
// this,
|
||||
// ).pushReplacement<T, void>(MaterialPageRoute(builder: (_) => page));
|
||||
// }
|
||||
|
||||
/// Pop current route
|
||||
void pop<T>([T? result]) {
|
||||
Navigator.of(this).pop(result);
|
||||
}
|
||||
// void pop<T>([T? result]) {
|
||||
// GoRouter.of(this).pop(result);
|
||||
// }
|
||||
|
||||
/// Pop until first route
|
||||
void popUntilFirst() {
|
||||
Navigator.of(this).popUntil((route) => route.isFirst);
|
||||
}
|
||||
// void popUntilFirst() {
|
||||
// Navigator.of(this).popUntil((route) => route.isFirst);
|
||||
// }
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -466,4 +468,26 @@ extension NumExtensions on num {
|
||||
final mod = math.pow(10.0, places);
|
||||
return ((this * mod).round().toDouble() / mod);
|
||||
}
|
||||
|
||||
/// Format as Vietnamese currency (đồng)
|
||||
/// Returns formatted string like "1.153.434đ"
|
||||
String get toVNCurrency {
|
||||
final formatter = NumberFormat.currency(
|
||||
locale: 'vi_VN',
|
||||
symbol: 'đ',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
return formatter.format(this);
|
||||
}
|
||||
|
||||
/// Format as Vietnamese currency with custom symbol
|
||||
/// Returns formatted string with custom symbol
|
||||
String toCurrency({String symbol = 'đ', int decimalDigits = 0}) {
|
||||
final formatter = NumberFormat.currency(
|
||||
locale: 'vi_VN',
|
||||
symbol: symbol,
|
||||
decimalDigits: decimalDigits,
|
||||
);
|
||||
return formatter.format(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -657,7 +657,7 @@ class AddressFormPage extends HookConsumerWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
value: items.containsKey(value) ? value : null,
|
||||
initialValue: items.containsKey(value) ? value : null,
|
||||
isExpanded: true,
|
||||
validator: validator,
|
||||
decoration: InputDecoration(
|
||||
@@ -796,7 +796,7 @@ class AddressFormPage extends HookConsumerWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
value: items.containsKey(value) ? value : null,
|
||||
initialValue: items.containsKey(value) ? value : null,
|
||||
isExpanded: true,
|
||||
validator: validator,
|
||||
decoration: InputDecoration(
|
||||
|
||||
@@ -453,7 +453,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
|
||||
borderRadius: BorderRadius.circular(AppRadius.card),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
@@ -759,7 +759,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
|
||||
return customerGroupsAsync.when(
|
||||
data: (groups) {
|
||||
return DropdownButtonFormField<CustomerGroup>(
|
||||
value: _selectedRole,
|
||||
initialValue: _selectedRole,
|
||||
decoration: _buildInputDecoration(
|
||||
hintText: 'Chọn vai trò',
|
||||
prefixIcon: FontAwesomeIcons.briefcase,
|
||||
@@ -831,7 +831,7 @@ class _RegisterPageState extends ConsumerState<RegisterPage> {
|
||||
return citiesAsync.when(
|
||||
data: (cities) {
|
||||
return DropdownButtonFormField<City>(
|
||||
value: _selectedCity,
|
||||
initialValue: _selectedCity,
|
||||
decoration: _buildInputDecoration(
|
||||
hintText: 'Chọn tỉnh/thành phố',
|
||||
prefixIcon: Icons.location_city,
|
||||
|
||||
@@ -55,7 +55,7 @@ class RoleDropdown extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DropdownButtonFormField<String>(
|
||||
value: value,
|
||||
initialValue: value,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Chọn vai trò của bạn',
|
||||
hintStyle: const TextStyle(
|
||||
|
||||
@@ -167,7 +167,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
|
||||
|
||||
// Price
|
||||
Text(
|
||||
'${currencyFormatter.format(widget.item.product.basePrice)}/${widget.item.product.unit ?? 'm²'}',
|
||||
'${currencyFormatter.format(widget.item.product.basePrice)}/m²',
|
||||
style: AppTypography.titleMedium.copyWith(
|
||||
color: AppColors.primaryBlue,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -252,7 +252,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
|
||||
|
||||
// Unit label
|
||||
Text(
|
||||
widget.item.product.unit ?? 'm²',
|
||||
'm²',
|
||||
style: AppTypography.bodySmall.copyWith(
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
@@ -273,7 +273,7 @@ class _CartItemWidgetState extends ConsumerState<CartItemWidget> {
|
||||
const TextSpan(text: '(Quy đổi: '),
|
||||
TextSpan(
|
||||
text:
|
||||
'${widget.item.quantityConverted.toStringAsFixed(2)} ${widget.item.product.unit ?? 'm²'}',
|
||||
'${widget.item.quantityConverted.toStringAsFixed(2)} m²',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const TextSpan(text: ' = '),
|
||||
|
||||
@@ -31,7 +31,7 @@ class MemberCardWidget extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
@@ -64,7 +64,7 @@ class MemberCardWidget extends StatelessWidget {
|
||||
Text(
|
||||
memberCard.memberType.displayName,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.8,
|
||||
@@ -79,7 +79,7 @@ class MemberCardWidget extends StatelessWidget {
|
||||
Text(
|
||||
'Valid through',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
color: Colors.white.withValues(alpha: 0.8),
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
@@ -123,7 +123,7 @@ class MemberCardWidget extends StatelessWidget {
|
||||
Text(
|
||||
'CLASS: ${memberCard.tier.displayName}',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
@@ -132,7 +132,7 @@ class MemberCardWidget extends StatelessWidget {
|
||||
Text(
|
||||
'Points: ${_formatPoints(memberCard.points)}',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
color: Colors.white.withValues(alpha: 0.9),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
|
||||
@@ -24,8 +24,8 @@ class OrderDetailModel {
|
||||
final List<OrderItemDetailModel> items;
|
||||
final PaymentTermsInfoModel paymentTerms;
|
||||
final List<TimelineItemModel> timeline;
|
||||
final List<dynamic> payments;
|
||||
final List<dynamic> invoices;
|
||||
final List<PaymentInfoModel> payments;
|
||||
final List<InvoiceInfoModel> invoices;
|
||||
|
||||
/// Create from JSON
|
||||
factory OrderDetailModel.fromJson(Map<String, dynamic> json) {
|
||||
@@ -50,8 +50,14 @@ class OrderDetailModel {
|
||||
.map((item) =>
|
||||
TimelineItemModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList(),
|
||||
payments: json['payments'] as List<dynamic>? ?? [],
|
||||
invoices: json['invoices'] as List<dynamic>? ?? [],
|
||||
payments: (json['payments'] as List<dynamic>? ?? [])
|
||||
.map((item) =>
|
||||
PaymentInfoModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList(),
|
||||
invoices: (json['invoices'] as List<dynamic>? ?? [])
|
||||
.map((item) =>
|
||||
InvoiceInfoModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,8 +70,8 @@ class OrderDetailModel {
|
||||
'items': items.map((item) => item.toJson()).toList(),
|
||||
'payment_terms': paymentTerms.toJson(),
|
||||
'timeline': timeline.map((item) => item.toJson()).toList(),
|
||||
'payments': payments,
|
||||
'invoices': invoices,
|
||||
'payments': payments.map((item) => item.toJson()).toList(),
|
||||
'invoices': invoices.map((item) => item.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -78,8 +84,8 @@ class OrderDetailModel {
|
||||
items: items.map((item) => item.toEntity()).toList(),
|
||||
paymentTerms: paymentTerms.toEntity(),
|
||||
timeline: timeline.map((item) => item.toEntity()).toList(),
|
||||
payments: payments,
|
||||
invoices: invoices,
|
||||
payments: payments.map((item) => item.toEntity()).toList(),
|
||||
invoices: invoices.map((item) => item.toEntity()).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -96,8 +102,12 @@ class OrderDetailModel {
|
||||
timeline: entity.timeline
|
||||
.map((item) => TimelineItemModel.fromEntity(item))
|
||||
.toList(),
|
||||
payments: entity.payments,
|
||||
invoices: entity.invoices,
|
||||
payments: entity.payments
|
||||
.map((item) => PaymentInfoModel.fromEntity(item))
|
||||
.toList(),
|
||||
invoices: entity.invoices
|
||||
.map((item) => InvoiceInfoModel.fromEntity(item))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -500,3 +510,93 @@ class TimelineItemModel {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Payment Info Model
|
||||
class PaymentInfoModel {
|
||||
const PaymentInfoModel({
|
||||
required this.name,
|
||||
required this.creationDate,
|
||||
required this.amount,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String creationDate;
|
||||
final double amount;
|
||||
|
||||
factory PaymentInfoModel.fromJson(Map<String, dynamic> json) {
|
||||
return PaymentInfoModel(
|
||||
name: json['name'] as String,
|
||||
creationDate: json['creation_date'] as String,
|
||||
amount: (json['amount'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'name': name,
|
||||
'creation_date': creationDate,
|
||||
'amount': amount,
|
||||
};
|
||||
}
|
||||
|
||||
PaymentInfo toEntity() {
|
||||
return PaymentInfo(
|
||||
name: name,
|
||||
creationDate: creationDate,
|
||||
amount: amount,
|
||||
);
|
||||
}
|
||||
|
||||
factory PaymentInfoModel.fromEntity(PaymentInfo entity) {
|
||||
return PaymentInfoModel(
|
||||
name: entity.name,
|
||||
creationDate: entity.creationDate,
|
||||
amount: entity.amount,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoice Info Model
|
||||
class InvoiceInfoModel {
|
||||
const InvoiceInfoModel({
|
||||
required this.name,
|
||||
required this.postingDate,
|
||||
required this.grandTotal,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String postingDate;
|
||||
final double grandTotal;
|
||||
|
||||
factory InvoiceInfoModel.fromJson(Map<String, dynamic> json) {
|
||||
return InvoiceInfoModel(
|
||||
name: json['name'] as String,
|
||||
postingDate: json['posting_date'] as String,
|
||||
grandTotal: (json['grand_total'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'name': name,
|
||||
'posting_date': postingDate,
|
||||
'grand_total': grandTotal,
|
||||
};
|
||||
}
|
||||
|
||||
InvoiceInfo toEntity() {
|
||||
return InvoiceInfo(
|
||||
name: name,
|
||||
postingDate: postingDate,
|
||||
grandTotal: grandTotal,
|
||||
);
|
||||
}
|
||||
|
||||
factory InvoiceInfoModel.fromEntity(InvoiceInfo entity) {
|
||||
return InvoiceInfoModel(
|
||||
name: entity.name,
|
||||
postingDate: entity.postingDate,
|
||||
grandTotal: entity.grandTotal,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ class OrderDetail extends Equatable {
|
||||
final List<OrderItemDetail> items;
|
||||
final PaymentTermsInfo paymentTerms;
|
||||
final List<TimelineItem> timeline;
|
||||
final List<dynamic> payments; // Payment entities can be added later
|
||||
final List<dynamic> invoices; // Invoice entities can be added later
|
||||
final List<PaymentInfo> payments;
|
||||
final List<InvoiceInfo> invoices;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
@@ -219,3 +219,35 @@ class TimelineItem extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [label, value, status];
|
||||
}
|
||||
|
||||
/// Payment Info
|
||||
class PaymentInfo extends Equatable {
|
||||
const PaymentInfo({
|
||||
required this.name,
|
||||
required this.creationDate,
|
||||
required this.amount,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String creationDate;
|
||||
final double amount;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [name, creationDate, amount];
|
||||
}
|
||||
|
||||
/// Invoice Info
|
||||
class InvoiceInfo extends Equatable {
|
||||
const InvoiceInfo({
|
||||
required this.name,
|
||||
required this.postingDate,
|
||||
required this.grandTotal,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String postingDate;
|
||||
final double grandTotal;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [name, postingDate, grandTotal];
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:worker/core/constants/ui_constants.dart';
|
||||
import 'package:worker/core/enums/status_color.dart';
|
||||
import 'package:worker/core/theme/colors.dart';
|
||||
import 'package:worker/core/utils/extensions.dart';
|
||||
import 'package:worker/features/orders/domain/entities/order_detail.dart';
|
||||
import 'package:worker/features/orders/presentation/providers/orders_provider.dart';
|
||||
|
||||
@@ -82,16 +83,24 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
_buildStatusTimelineCard(orderDetail),
|
||||
|
||||
// Delivery/Address Information Card
|
||||
_buildAddressInfoCard(orderDetail),
|
||||
_buildAddressInfoCard(context, orderDetail),
|
||||
|
||||
// Customer Information Card
|
||||
_buildCustomerInfoCard(orderDetail),
|
||||
// Invoice Information Card
|
||||
_buildInvoiceInfoCard(context, orderDetail),
|
||||
|
||||
// Invoices List Card
|
||||
_buildInvoicesListCard(context, orderDetail),
|
||||
|
||||
// Products List Card
|
||||
_buildProductsListCard(orderDetail),
|
||||
|
||||
// Order Summary Card
|
||||
_buildOrderSummaryCard(orderDetail),
|
||||
|
||||
// Payment History Card
|
||||
_buildPaymentHistoryCard(context, orderDetail),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -327,7 +336,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
}
|
||||
|
||||
/// Build Address Info Card
|
||||
Widget _buildAddressInfoCard(OrderDetail orderDetail) {
|
||||
Widget _buildAddressInfoCard(BuildContext context, OrderDetail orderDetail) {
|
||||
final order = orderDetail.order;
|
||||
final shippingAddress = orderDetail.shippingAddress;
|
||||
final dateFormatter = DateFormat('dd/MM/yyyy');
|
||||
@@ -341,15 +350,15 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
const Row(
|
||||
children: [
|
||||
const FaIcon(
|
||||
FaIcon(
|
||||
FontAwesomeIcons.truck,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Thông tin giao hàng',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
@@ -362,86 +371,139 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Delivery Date
|
||||
_buildInfoRow(
|
||||
icon: FontAwesomeIcons.calendar,
|
||||
label: 'Ngày giao hàng',
|
||||
value: dateFormatter.format(DateTime.parse(order.deliveryDate)),
|
||||
// Address Section with Label + Button
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'Địa chỉ nhận hàng',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// TODO: Navigate to address update
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chức năng đang phát triển')),
|
||||
);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
minimumSize: Size.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
side: const BorderSide(color: AppColors.grey100),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Cập nhật',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primaryBlue,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
_buildInfoRow(
|
||||
icon: FontAwesomeIcons.locationDot,
|
||||
label: 'Địa chỉ giao hàng',
|
||||
value:
|
||||
'${shippingAddress.addressLine1}\n${shippingAddress.wardName}, ${shippingAddress.cityName}',
|
||||
// Address Box
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
shippingAddress.addressTitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
shippingAddress.phone,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${shippingAddress.addressLine1}\n${shippingAddress.wardName}, ${shippingAddress.cityName}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildInfoRow(
|
||||
icon: FontAwesomeIcons.user,
|
||||
label: 'Người nhận',
|
||||
value: '${shippingAddress.addressTitle} - ${shippingAddress.phone}',
|
||||
// Pickup Date
|
||||
const Text(
|
||||
'Ngày lấy hàng',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
dateFormatter.format(DateTime.parse(order.deliveryDate)),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
|
||||
if (order.description.isNotEmpty) ...[ const SizedBox(height: 16),
|
||||
|
||||
// Notes
|
||||
const Text(
|
||||
'Ghi chú',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
order.description,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Info Row
|
||||
Widget _buildInfoRow({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required String value,
|
||||
Color? valueColor,
|
||||
}) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Row(
|
||||
children: [
|
||||
FaIcon(icon, size: 14, color: AppColors.grey500),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
value,
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: valueColor != null
|
||||
? FontWeight.w600
|
||||
: FontWeight.w500,
|
||||
color: valueColor ?? AppColors.grey900,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Customer Info Card
|
||||
Widget _buildCustomerInfoCard(OrderDetail orderDetail) {
|
||||
final order = orderDetail.order;
|
||||
/// Build Invoice Info Card
|
||||
Widget _buildInvoiceInfoCard(BuildContext context, OrderDetail orderDetail) {
|
||||
final billingAddress = orderDetail.billingAddress;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
elevation: 1,
|
||||
@@ -451,16 +513,146 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Title + Update Button
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const FaIcon(
|
||||
FontAwesomeIcons.user,
|
||||
const Row(
|
||||
children: [
|
||||
FaIcon(
|
||||
FontAwesomeIcons.fileInvoice,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Thông tin hóa đơn',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// TODO: Navigate to invoice update
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chức năng đang phát triển')),
|
||||
);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
minimumSize: Size.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
side: BorderSide(color: AppColors.grey100),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Cập nhật',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primaryBlue,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Invoice Address Box
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
billingAddress.addressTitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
if (billingAddress.taxCode.isNotEmpty) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Mã số thuế: ${billingAddress.taxCode}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Số điện thoại: ${billingAddress.phone}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Email: ${billingAddress.email}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Địa chỉ: ${billingAddress.addressLine1}, ${billingAddress.wardName}, ${billingAddress.cityName}',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Invoices List Card
|
||||
Widget _buildInvoicesListCard(BuildContext context, OrderDetail orderDetail) {
|
||||
final invoices = orderDetail.invoices;
|
||||
|
||||
if (invoices.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
FaIcon(
|
||||
FontAwesomeIcons.fileInvoiceDollar,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'Thông tin khách hàng',
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Hóa đơn đã xuất',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -472,54 +664,109 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildCustomerRow('Tên khách hàng:', order.customer),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
_buildCustomerRow('Số điện thoại:', billingAddress.phone),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
_buildCustomerRow('Email:', billingAddress.email),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
if (billingAddress.taxCode.isNotEmpty) ...[
|
||||
_buildCustomerRow('Mã số thuế:', billingAddress.taxCode),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
// Invoice Items (Mock data for now)
|
||||
...invoices.map((e) => _buildInvoiceItem(
|
||||
invoiceId: e.name,
|
||||
date: e.postingDate,
|
||||
amount: e.grandTotal.toVNCurrency,
|
||||
onTap: () {
|
||||
// TODO: Navigate to invoice detail
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chức năng đang phát triển')),
|
||||
);
|
||||
},
|
||||
),),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Customer Row
|
||||
Widget _buildCustomerRow(String label, String value) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
/// Build Invoice Item
|
||||
Widget _buildInvoiceItem({
|
||||
required String invoiceId,
|
||||
required String date,
|
||||
required String amount,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [AppColors.primaryBlue, Color(0xFF1d4ed8)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Center(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.fileInvoice,
|
||||
color: Colors.white,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
invoiceId,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'Ngày xuất: $date',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4,),
|
||||
Text(
|
||||
amount,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.danger,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const FaIcon(
|
||||
FontAwesomeIcons.chevronRight,
|
||||
size: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Products List Card
|
||||
Widget _buildProductsListCard(OrderDetail orderDetail) {
|
||||
final items = orderDetail.items;
|
||||
final currencyFormatter = NumberFormat.currency(
|
||||
locale: 'vi_VN',
|
||||
symbol: 'đ',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
@@ -530,15 +777,15 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
const Row(
|
||||
children: [
|
||||
const FaIcon(
|
||||
FaIcon(
|
||||
FontAwesomeIcons.box,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Sản phẩm đặt hàng',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
@@ -657,14 +904,14 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
'${currencyFormatter.format(item.price)}/m²',
|
||||
'${item.price.toVNCurrency}/m²',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
currencyFormatter.format(item.totalAmount),
|
||||
item.totalAmount.toVNCurrency,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -692,11 +939,6 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
Widget _buildOrderSummaryCard(OrderDetail orderDetail) {
|
||||
final order = orderDetail.order;
|
||||
final paymentTerms = orderDetail.paymentTerms;
|
||||
final currencyFormatter = NumberFormat.currency(
|
||||
locale: 'vi_VN',
|
||||
symbol: 'đ',
|
||||
decimalDigits: 0,
|
||||
);
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
@@ -728,13 +970,13 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
_buildSummaryRow('Tổng tiền hàng:', currencyFormatter.format(order.total)),
|
||||
_buildSummaryRow('Tổng tiền hàng:', order.total.toVNCurrency),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
if (order.totalRemaining > 0) ...[
|
||||
_buildSummaryRow(
|
||||
'Còn lại:',
|
||||
currencyFormatter.format(order.totalRemaining),
|
||||
order.totalRemaining.toVNCurrency,
|
||||
valueColor: AppColors.warning,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
@@ -744,7 +986,7 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
_buildSummaryRow(
|
||||
'Tổng cộng:',
|
||||
currencyFormatter.format(order.grandTotal),
|
||||
order.grandTotal.toVNCurrency,
|
||||
isTotal: true,
|
||||
),
|
||||
|
||||
@@ -784,31 +1026,169 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
|
||||
if (order.description.isNotEmpty) ...[
|
||||
const Divider(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Order Notes
|
||||
Row(
|
||||
/// Build Payment History Card
|
||||
Widget _buildPaymentHistoryCard(BuildContext context, OrderDetail orderDetail) {
|
||||
final order = orderDetail.order;
|
||||
final payments = orderDetail.payments;
|
||||
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 16,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
FaIcon(
|
||||
FontAwesomeIcons.clockRotateLeft,
|
||||
color: AppColors.primaryBlue,
|
||||
size: 18,
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Lịch sử thanh toán',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
|
||||
...payments.map((e) => _buildPaymentItem(
|
||||
paymentId: e.name,
|
||||
date: e.creationDate,
|
||||
amount: e.amount.toVNCurrency,
|
||||
onTap: () {
|
||||
// TODO: Show payment detail modal
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Chi tiết thanh toán')),
|
||||
);
|
||||
},
|
||||
)),
|
||||
|
||||
|
||||
// Payment Summary
|
||||
Container(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(color: AppColors.grey100, width: 1),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const FaIcon(FontAwesomeIcons.noteSticky, size: 14, color: AppColors.grey500),
|
||||
const SizedBox(width: 6),
|
||||
const Text(
|
||||
'Ghi chú đơn hàng:',
|
||||
style: TextStyle(fontSize: 14, color: AppColors.grey500),
|
||||
'Còn lại:',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
order.totalRemaining.toVNCurrency,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.danger,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
order.description,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey900,
|
||||
height: 1.4,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Build Payment Item
|
||||
Widget _buildPaymentItem({
|
||||
required String paymentId,
|
||||
required String date,
|
||||
required String amount,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFD1FAE5),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Center(
|
||||
child: FaIcon(
|
||||
FontAwesomeIcons.check,
|
||||
color: Color(0xFF065F46),
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
paymentId,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey900,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
date,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
amount,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF065F46),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const FaIcon(
|
||||
FontAwesomeIcons.chevronRight,
|
||||
size: 14,
|
||||
color: AppColors.grey500,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -848,8 +1228,6 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
|
||||
/// Build Action Buttons
|
||||
Widget _buildActionButtons(BuildContext context, OrderDetail orderDetail) {
|
||||
final shippingAddress = orderDetail.shippingAddress;
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
@@ -866,23 +1244,22 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
spacing: 12,
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton.icon(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
// TODO: Navigate to payment page
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Gọi ${shippingAddress.phone}...'),
|
||||
const SnackBar(
|
||||
content: Text('Chức năng thanh toán đang phát triển'),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const FaIcon(FontAwesomeIcons.phone, size: 18),
|
||||
label: const Text('Liên hệ'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
side: const BorderSide(
|
||||
color: AppColors.grey100,
|
||||
width: 2,
|
||||
),
|
||||
foregroundColor: AppColors.grey900,
|
||||
icon: const FaIcon(FontAwesomeIcons.creditCard, size: 18),
|
||||
label: const Text('Thanh toán'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
backgroundColor: AppColors.primaryBlue,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
@@ -890,21 +1267,24 @@ class OrderDetailPage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ElevatedButton.icon(
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () {
|
||||
// TODO: Navigate to chat/support
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Chức năng đang phát triển...'),
|
||||
content: Text('Liên hệ hỗ trợ...'),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const FaIcon(FontAwesomeIcons.penToSquare, size: 18),
|
||||
label: const Text('Cập nhật'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
backgroundColor: AppColors.primaryBlue,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
icon: const FaIcon(FontAwesomeIcons.comments, size: 18),
|
||||
label: const Text('Liên hệ hỗ trợ'),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
side: BorderSide(
|
||||
color: AppColors.grey100,
|
||||
width: 2,
|
||||
),
|
||||
foregroundColor: AppColors.grey900,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
|
||||
@@ -23,7 +23,7 @@ class DocumentCard extends StatelessWidget {
|
||||
border: Border.all(color: AppColors.grey100),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.05),
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
|
||||
@@ -28,7 +28,6 @@ class ProductModel extends HiveObject {
|
||||
this.specifications,
|
||||
this.itemGroupName,
|
||||
this.brand,
|
||||
this.unit,
|
||||
this.conversionOfSm,
|
||||
this.introAttributes,
|
||||
required this.isActive,
|
||||
@@ -83,10 +82,6 @@ class ProductModel extends HiveObject {
|
||||
@HiveField(10)
|
||||
final String? brand;
|
||||
|
||||
/// Unit of measurement (m2, box, piece, etc.)
|
||||
@HiveField(11)
|
||||
final String? unit;
|
||||
|
||||
/// Conversion factor for Square Meter UOM (tiles per m²)
|
||||
/// Used to calculate: Số viên = Số lượng × conversionOfSm
|
||||
@HiveField(17)
|
||||
@@ -204,7 +199,6 @@ class ProductModel extends HiveObject {
|
||||
: null,
|
||||
itemGroupName: json['item_group_name'] as String?,
|
||||
brand: json['brand'] as String?,
|
||||
unit: json['currency'] as String?, // Use currency as unit for now
|
||||
conversionOfSm: json['conversion_of_sm'] != null
|
||||
? (json['conversion_of_sm'] as num).toDouble()
|
||||
: null,
|
||||
@@ -260,7 +254,6 @@ class ProductModel extends HiveObject {
|
||||
specifications: null,
|
||||
itemGroupName: json['item_group_name'] as String?,
|
||||
brand: null, // Not provided by wishlist API
|
||||
unit: json['currency'] as String? ?? 'm²',
|
||||
conversionOfSm: json['conversion_of_sm'] != null
|
||||
? (json['conversion_of_sm'] as num).toDouble()
|
||||
: null,
|
||||
@@ -291,7 +284,6 @@ class ProductModel extends HiveObject {
|
||||
: null,
|
||||
'item_group_name': itemGroupName,
|
||||
'brand': brand,
|
||||
'unit': unit,
|
||||
'conversion_of_sm': conversionOfSm,
|
||||
'intro_attributes': introAttributes != null
|
||||
? jsonDecode(introAttributes!)
|
||||
@@ -406,7 +398,6 @@ class ProductModel extends HiveObject {
|
||||
String? specifications,
|
||||
String? itemGroupName,
|
||||
String? brand,
|
||||
String? unit,
|
||||
double? conversionOfSm,
|
||||
String? introAttributes,
|
||||
bool? isActive,
|
||||
@@ -427,7 +418,6 @@ class ProductModel extends HiveObject {
|
||||
specifications: specifications ?? this.specifications,
|
||||
itemGroupName: itemGroupName ?? this.itemGroupName,
|
||||
brand: brand ?? this.brand,
|
||||
unit: unit ?? this.unit,
|
||||
conversionOfSm: conversionOfSm ?? this.conversionOfSm,
|
||||
introAttributes: introAttributes ?? this.introAttributes,
|
||||
isActive: isActive ?? this.isActive,
|
||||
@@ -471,7 +461,6 @@ class ProductModel extends HiveObject {
|
||||
specifications: specificationsMap ?? {},
|
||||
itemGroupName: itemGroupName,
|
||||
brand: brand,
|
||||
unit: unit,
|
||||
conversionOfSm: conversionOfSm,
|
||||
introAttributes: introAttributesList,
|
||||
isActive: isActive,
|
||||
|
||||
@@ -28,7 +28,6 @@ class ProductModelAdapter extends TypeAdapter<ProductModel> {
|
||||
specifications: fields[8] as String?,
|
||||
itemGroupName: fields[9] as String?,
|
||||
brand: fields[10] as String?,
|
||||
unit: fields[11] as String?,
|
||||
conversionOfSm: (fields[17] as num?)?.toDouble(),
|
||||
introAttributes: fields[18] as String?,
|
||||
isActive: fields[12] as bool,
|
||||
@@ -42,7 +41,7 @@ class ProductModelAdapter extends TypeAdapter<ProductModel> {
|
||||
@override
|
||||
void write(BinaryWriter writer, ProductModel obj) {
|
||||
writer
|
||||
..writeByte(19)
|
||||
..writeByte(18)
|
||||
..writeByte(0)
|
||||
..write(obj.productId)
|
||||
..writeByte(1)
|
||||
@@ -65,8 +64,6 @@ class ProductModelAdapter extends TypeAdapter<ProductModel> {
|
||||
..write(obj.itemGroupName)
|
||||
..writeByte(10)
|
||||
..write(obj.brand)
|
||||
..writeByte(11)
|
||||
..write(obj.unit)
|
||||
..writeByte(12)
|
||||
..write(obj.isActive)
|
||||
..writeByte(13)
|
||||
|
||||
@@ -22,7 +22,6 @@ class Product {
|
||||
required this.specifications,
|
||||
this.itemGroupName,
|
||||
this.brand,
|
||||
this.unit,
|
||||
this.conversionOfSm,
|
||||
this.introAttributes,
|
||||
required this.isActive,
|
||||
@@ -64,9 +63,6 @@ class Product {
|
||||
/// Brand name
|
||||
final String? brand;
|
||||
|
||||
/// Unit of measurement (e.g., "m²", "viên", "hộp")
|
||||
final String? unit;
|
||||
|
||||
/// Conversion factor for Square Meter UOM (tiles per m²)
|
||||
/// Used to calculate: Số viên = Số lượng × conversionOfSm
|
||||
final double? conversionOfSm;
|
||||
@@ -154,7 +150,6 @@ class Product {
|
||||
Map<String, dynamic>? specifications,
|
||||
String? itemGroupName,
|
||||
String? brand,
|
||||
String? unit,
|
||||
double? conversionOfSm,
|
||||
List<Map<String, String>>? introAttributes,
|
||||
bool? isActive,
|
||||
@@ -175,7 +170,6 @@ class Product {
|
||||
specifications: specifications ?? this.specifications,
|
||||
itemGroupName: itemGroupName ?? this.itemGroupName,
|
||||
brand: brand ?? this.brand,
|
||||
unit: unit ?? this.unit,
|
||||
conversionOfSm: conversionOfSm ?? this.conversionOfSm,
|
||||
introAttributes: introAttributes ?? this.introAttributes,
|
||||
isActive: isActive ?? this.isActive,
|
||||
@@ -203,7 +197,6 @@ class Product {
|
||||
other.basePrice == basePrice &&
|
||||
other.itemGroupName == itemGroupName &&
|
||||
other.brand == brand &&
|
||||
other.unit == unit &&
|
||||
other.isActive == isActive &&
|
||||
other.isFeatured == isFeatured &&
|
||||
other.erpnextItemCode == erpnextItemCode;
|
||||
@@ -218,7 +211,6 @@ class Product {
|
||||
basePrice,
|
||||
itemGroupName,
|
||||
brand,
|
||||
unit,
|
||||
isActive,
|
||||
isFeatured,
|
||||
erpnextItemCode,
|
||||
|
||||
@@ -156,7 +156,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Đã thêm $_quantity ${product.unit} ${product.name} vào giỏ hàng!',
|
||||
'Đã thêm $_quantity m² ${product.name} vào giỏ hàng!',
|
||||
),
|
||||
duration: const Duration(seconds: 2),
|
||||
action: SnackBarAction(
|
||||
@@ -246,7 +246,7 @@ class _ProductDetailPageState extends ConsumerState<ProductDetailPage> {
|
||||
right: 0,
|
||||
child: StickyActionBar(
|
||||
quantity: _quantity,
|
||||
unit: product.unit ?? 'm²',
|
||||
unit: 'm²',
|
||||
conversionOfSm: product.conversionOfSm,
|
||||
uomFromIntroAttributes: product.getIntroAttribute('UOM'),
|
||||
onIncrease: _increaseQuantity,
|
||||
|
||||
@@ -220,7 +220,7 @@ class ProductCard extends ConsumerWidget {
|
||||
|
||||
// Price
|
||||
Text(
|
||||
'${_formatPrice(product.effectivePrice)}/${product.unit}',
|
||||
'${_formatPrice(product.effectivePrice)}/m²',
|
||||
style: const TextStyle(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
||||
@@ -61,7 +61,7 @@ class ProductInfoSection extends StatelessWidget {
|
||||
children: [
|
||||
// Current Price
|
||||
Text(
|
||||
'${_formatPrice(product.basePrice)}/${product.unit ?? 'm²'}',
|
||||
'${_formatPrice(product.basePrice)}/m²',
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w700,
|
||||
|
||||
@@ -271,7 +271,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
value: selectedStyle.value.isEmpty
|
||||
initialValue: selectedStyle.value.isEmpty
|
||||
? null
|
||||
: selectedStyle.value,
|
||||
decoration: InputDecoration(
|
||||
@@ -365,7 +365,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
value: selectedBudget.value.isEmpty
|
||||
initialValue: selectedBudget.value.isEmpty
|
||||
? null
|
||||
: selectedBudget.value,
|
||||
decoration: InputDecoration(
|
||||
|
||||
@@ -124,7 +124,7 @@ class SearchAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
style: const TextStyle(color: Colors.white),
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText,
|
||||
hintStyle: TextStyle(color: Colors.white.withOpacity(0.7)),
|
||||
hintStyle: TextStyle(color: Colors.white.withValues(alpha: 0.7)),
|
||||
border: InputBorder.none,
|
||||
suffixIcon: controller?.text.isNotEmpty ?? false
|
||||
? IconButton(
|
||||
|
||||
@@ -45,7 +45,7 @@ class GradientCard extends StatelessWidget {
|
||||
shadows ??
|
||||
[
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1 * (elevation / 4)),
|
||||
color: Colors.black.withValues(alpha: 0.1 * (elevation / 4)),
|
||||
blurRadius: elevation,
|
||||
offset: Offset(0, elevation / 2),
|
||||
),
|
||||
@@ -243,9 +243,9 @@ class _ShimmerGradientCardState extends State<ShimmerGradientCard>
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Colors.white.withOpacity(0.1),
|
||||
Colors.white.withOpacity(0.3),
|
||||
Colors.white.withOpacity(0.1),
|
||||
Colors.white.withValues(alpha: 0.1),
|
||||
Colors.white.withValues(alpha: 0.3),
|
||||
Colors.white.withValues(alpha: 0.1),
|
||||
],
|
||||
stops: [
|
||||
_controller.value - 0.3,
|
||||
|
||||
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.0+10
|
||||
version: 1.0.1+12
|
||||
|
||||
environment:
|
||||
sdk: ^3.10.0
|
||||
|
||||
Reference in New Issue
Block a user