uodate create/detail - no upload file
This commit is contained in:
@@ -71,3 +71,38 @@ curl --location 'https://land.dbiz.com//api/method/building_material.building_ma
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#create new design request
|
||||||
|
curl --location 'https://land.dbiz.com//api/method/building_material.building_material.api.design_request.create' \
|
||||||
|
--header 'Cookie: sid=a0cbe3ea6f9a7e9cf083bbe3139eada68d2357eac0167bcc66cda17d; full_name=Ha%20Duy%20Lam; sid=a0cbe3ea6f9a7e9cf083bbe3139eada68d2357eac0167bcc66cda17d; system_user=yes; user_id=lamhd%40gmail.com; user_image=/files/avatar_0986788766_1763627962.jpg' \
|
||||||
|
--header 'X-Frappe-Csrf-Token: 6ff3be4d1f887dbebf86ba4502b05d94b30c0b0569de49b74a7171a9' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"subject": "Nhà phố 2 tầng",
|
||||||
|
"area": "150",
|
||||||
|
"region": "Quận 1, TP.HCM",
|
||||||
|
"desired_style": "Hiện đại",
|
||||||
|
"estimated_budget": "500 triệu",
|
||||||
|
"detailed_requirements": "Cần thiết kế phòng khách rộng, 3 phòng ngủ",
|
||||||
|
"dateline": "2025-12-31"
|
||||||
|
}'
|
||||||
|
#response
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"name": "ISS-2025-00006"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#upload file
|
||||||
|
curl --location 'https://land.dbiz.com//api/method/upload_file' \
|
||||||
|
--header 'Cookie: sid=a0cbe3ea6f9a7e9cf083bbe3139eada68d2357eac0167bcc66cda17d; full_name=Ha%20Duy%20Lam; sid=a0cbe3ea6f9a7e9cf083bbe3139eada68d2357eac0167bcc66cda17d; system_user=yes; user_id=lamhd%40gmail.com; user_image=/files/avatar_0986788766_1763627962.jpg' \
|
||||||
|
--header 'X-Frappe-Csrf-Token: 6ff3be4d1f887dbebf86ba4502b05d94b30c0b0569de49b74a7171a9' \
|
||||||
|
--form 'file=@"/C:/Users/tiennld/Downloads/b0d6423a04ce8890d1df.jpg"' \
|
||||||
|
--form 'is_private="0"' \
|
||||||
|
--form 'folder="Home/Attachments"' \
|
||||||
|
--form 'doctype="Issue"' \
|
||||||
|
--form 'docname="ISS-2025-00005"' \
|
||||||
|
--form 'optimize="true"'
|
||||||
@@ -49,6 +49,8 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
background: #d1fae5;
|
||||||
|
color: #065f46;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-pending {
|
.status-pending {
|
||||||
@@ -66,32 +68,93 @@
|
|||||||
color: #065f46;
|
color: #065f46;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-grid {
|
/* Description List Styles */
|
||||||
display: grid;
|
.description-list {
|
||||||
grid-template-columns: 1fr 1fr;
|
display: flex;
|
||||||
gap: 16px;
|
flex-direction: column;
|
||||||
margin-bottom: 24px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-item {
|
.description-item {
|
||||||
text-align: center;
|
display: flex;
|
||||||
padding: 16px 12px;
|
border-bottom: 1px solid #f3f4f6;
|
||||||
background: #f8fafc;
|
padding-bottom: 12px;
|
||||||
border-radius: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-label {
|
.description-item:last-child {
|
||||||
font-size: 12px;
|
border-bottom: none;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-label {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 120px;
|
||||||
|
font-size: 13px;
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
text-transform: uppercase;
|
padding-top: 2px;
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-value {
|
.description-value {
|
||||||
font-size: 16px;
|
flex: 1;
|
||||||
font-weight: 700;
|
font-size: 15px;
|
||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Floor Plan Styles */
|
||||||
|
.floor-plan-container {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-plan-thumbnail {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-plan-thumbnail:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-plan-image {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-plan-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 91, 154, 0.85);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-plan-thumbnail:hover .floor-plan-overlay {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-plan-overlay i {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floor-plan-overlay span {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-section {
|
.detail-section {
|
||||||
@@ -302,17 +365,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.info-grid {
|
.description-item {
|
||||||
grid-template-columns: 1fr;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-item {
|
.description-label {
|
||||||
display: flex;
|
width: auto;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
text-align: left;
|
|
||||||
padding: 12px 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
@@ -343,27 +402,31 @@
|
|||||||
<span class="status-badge" id="status-badge">Hoàn thành</span>
|
<span class="status-badge" id="status-badge">Hoàn thành</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Project Info Grid -->
|
<!-- Project Info - Simple Description List -->
|
||||||
<div class="info-grid">
|
<div class="detail-section" style="margin-bottom: 0;">
|
||||||
<div class="info-item">
|
<dl class="description-list">
|
||||||
<div class="info-label">Diện tích</div>
|
|
||||||
<div class="info-value" id="project-area">120m²</div>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<div class="info-label">Phong cách</div>
|
|
||||||
<div class="info-value" id="project-style">Hiện đại</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-grid">
|
<div class="detail-section">
|
||||||
<div class="info-item">
|
<h3 class="section-title">
|
||||||
<div class="info-label">Ngân sách</div>
|
<i class="fas fa-info-circle" style="color: #2563eb;"></i>
|
||||||
<div class="info-value" id="project-budget">300-500 triệu</div>
|
Thông tin thiết kế
|
||||||
</div>
|
</h3>
|
||||||
<div class="info-item">
|
|
||||||
<div class="info-label">Trạng thái</div>
|
<dl class="description-list">
|
||||||
<div class="info-value" id="project-status">Đã hoàn thành</div>
|
<div class="description-item">
|
||||||
</div>
|
<dt class="description-label">Tên công trình:</dt>
|
||||||
|
<dd class="description-value" id="project-name">Thiết kế nhà phố 3 tầng - Anh Minh (Quận 7)</dd>
|
||||||
|
</div>
|
||||||
|
<div class="description-item">
|
||||||
|
<dt class="description-label">Mô tả chi tiết:</dt>
|
||||||
|
<dd class="description-value" id="project-notes">
|
||||||
|
Diện tích: 85 m² <br>
|
||||||
|
Khu vực: Hồ Chí Minh <br>
|
||||||
|
Phong cách mong muốn: Hiện đại <br>
|
||||||
|
Ngân sách dự kiến: Trao đổi trực tiếp <br>
|
||||||
|
Yêu cầu chi tiết: Thiết kế với 4 phòng ngủ, 3 phòng tắm, phòng khách rộng rãi và khu bếp mở. Ưu tiên sử dụng gạch men màu sáng để tạo cảm giác thoáng đãng.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -379,40 +442,8 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Project Details -->
|
<!-- Floor Plan Image -->
|
||||||
<div class="detail-card">
|
<div class="detail-card">
|
||||||
<div class="detail-section">
|
|
||||||
<h3 class="section-title">
|
|
||||||
<i class="fas fa-info-circle" style="color: #2563eb;"></i>
|
|
||||||
Thông tin dự án
|
|
||||||
</h3>
|
|
||||||
<div class="section-content">
|
|
||||||
<p><strong>Tên dự án:</strong> <span id="project-name">Thiết kế nhà phố 3 tầng - Anh Minh (Quận 7)</span></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="detail-section">
|
|
||||||
<h3 class="section-title">
|
|
||||||
<i class="fas fa-edit" style="color: #2563eb;"></i>
|
|
||||||
Mô tả yêu cầu
|
|
||||||
</h3>
|
|
||||||
<div class="section-content" id="project-description">
|
|
||||||
Thiết kế nhà phố 3 tầng phong cách hiện đại với 4 phòng ngủ, 3 phòng tắm, phòng khách rộng rãi và khu bếp mở.
|
|
||||||
Ưu tiên sử dụng gạch men màu sáng để tạo cảm giác thoáng đãng. Tầng 1: garage, phòng khách, bếp.
|
|
||||||
Tầng 2: 2 phòng ngủ, 2 phòng tắm. Tầng 3: phòng ngủ master, phòng làm việc, sân thượng.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="detail-section">
|
|
||||||
<h3 class="section-title">
|
|
||||||
<i class="fas fa-phone" style="color: #2563eb;"></i>
|
|
||||||
Thông tin liên hệ
|
|
||||||
</h3>
|
|
||||||
<div class="section-content" id="contact-info">
|
|
||||||
SĐT: 0901234567 | Email: minh.nguyen@email.com
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="detail-section">
|
<div class="detail-section">
|
||||||
<h3 class="section-title">
|
<h3 class="section-title">
|
||||||
<i class="fas fa-paperclip" style="color: #2563eb;"></i>
|
<i class="fas fa-paperclip" style="color: #2563eb;"></i>
|
||||||
@@ -440,7 +471,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Status Timeline -->
|
<!-- Status Timeline -->
|
||||||
<div class="detail-card">
|
<!--<div class="detail-card">
|
||||||
<h3 class="section-title">
|
<h3 class="section-title">
|
||||||
<i class="fas fa-history" style="color: #2563eb;"></i>
|
<i class="fas fa-history" style="color: #2563eb;"></i>
|
||||||
Lịch sử trạng thái
|
Lịch sử trạng thái
|
||||||
@@ -491,14 +522,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>-->
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
<!-- Action Buttons -->
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<button class="btn btn-secondary" onclick="editRequest()">
|
<!--<button class="btn btn-secondary" onclick="editRequest()">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
Chỉnh sửa
|
Chỉnh sửa
|
||||||
</button>
|
</button>-->
|
||||||
<button class="btn btn-primary" onclick="contactSupport()">
|
<button class="btn btn-primary" onclick="contactSupport()">
|
||||||
<i class="fas fa-comments"></i>
|
<i class="fas fa-comments"></i>
|
||||||
Liên hệ
|
Liên hệ
|
||||||
@@ -536,28 +567,26 @@
|
|||||||
const requestDatabase = {
|
const requestDatabase = {
|
||||||
'YC001': {
|
'YC001': {
|
||||||
id: 'YC001',
|
id: 'YC001',
|
||||||
|
status: 'completed',
|
||||||
|
statusText: 'Đã hoàn thành',
|
||||||
name: 'Thiết kế nhà phố 3 tầng - Anh Minh (Quận 7)',
|
name: 'Thiết kế nhà phố 3 tầng - Anh Minh (Quận 7)',
|
||||||
area: '120m²',
|
area: '120m²',
|
||||||
style: 'Hiện đại',
|
style: 'Hiện đại',
|
||||||
budget: '300-500 triệu',
|
budget: '300-500 triệu',
|
||||||
status: 'completed',
|
notes: 'Ưu tiên sử dụng gạch men màu sáng để tạo cảm giác thoáng đãng.',
|
||||||
statusText: 'Đã hoàn thành',
|
|
||||||
description: 'Thiết kế nhà phố 3 tầng phong cách hiện đại với 4 phòng ngủ, 3 phòng tắm, phòng khách rộng rãi và khu bếp mở. Ưu tiên sử dụng gạch men màu sáng để tạo cảm giác thoáng đãng. Tầng 1: garage, phòng khách, bếp. Tầng 2: 2 phòng ngủ, 2 phòng tắm. Tầng 3: phòng ngủ master, phòng làm việc, sân thượng.',
|
|
||||||
contact: 'SĐT: 0901234567 | Email: minh.nguyen@email.com',
|
|
||||||
createdDate: '20/10/2024',
|
createdDate: '20/10/2024',
|
||||||
files: ['mat-bang-hien-tai.jpg', 'ban-ve-kien-truc.dwg'],
|
files: ['mat-bang-hien-tai.jpg', 'ban-ve-kien-truc.dwg'],
|
||||||
designLink: 'https://example.com/3d-design/YC001'
|
designLink: 'https://example.com/3d-design/YC001'
|
||||||
},
|
},
|
||||||
'YC002': {
|
'YC002': {
|
||||||
id: 'YC002',
|
id: 'YC002',
|
||||||
|
status: 'designing',
|
||||||
|
statusText: 'Đang thiết kế',
|
||||||
name: 'Cải tạo căn hộ chung cư - Chị Lan (Quận 2)',
|
name: 'Cải tạo căn hộ chung cư - Chị Lan (Quận 2)',
|
||||||
area: '85m²',
|
area: '85m²',
|
||||||
style: 'Scandinavian',
|
style: 'Scandinavian',
|
||||||
budget: '100-300 triệu',
|
budget: '100-300 triệu',
|
||||||
status: 'designing',
|
notes: 'Tối ưu không gian lưu trữ, sử dụng gạch men màu sáng và gỗ tự nhiên.',
|
||||||
statusText: 'Đang thiết kế',
|
|
||||||
description: 'Cải tạo căn hộ chung cư 3PN theo phong cách Scandinavian. Tối ưu không gian lưu trữ, sử dụng gạch men màu sáng và gỗ tự nhiên.',
|
|
||||||
contact: 'SĐT: 0987654321',
|
|
||||||
createdDate: '25/10/2024',
|
createdDate: '25/10/2024',
|
||||||
files: ['hinh-anh-hien-trang.jpg'],
|
files: ['hinh-anh-hien-trang.jpg'],
|
||||||
designLink: null
|
designLink: null
|
||||||
@@ -565,13 +594,12 @@
|
|||||||
'YC003': {
|
'YC003': {
|
||||||
id: 'YC003',
|
id: 'YC003',
|
||||||
name: 'Thiết kế biệt thự 2 tầng - Anh Đức (Bình Dương)',
|
name: 'Thiết kế biệt thự 2 tầng - Anh Đức (Bình Dương)',
|
||||||
|
status: 'pending',
|
||||||
|
statusText: 'Chờ tiếp nhận',
|
||||||
area: '200m²',
|
area: '200m²',
|
||||||
style: 'Luxury',
|
style: 'Luxury',
|
||||||
budget: 'Trên 1 tỷ',
|
budget: 'Trên 1 tỷ',
|
||||||
status: 'pending',
|
notes: 'Thiết kế biệt thự có hồ bơi và sân vườn, 5 phòng ngủ, garage 2 xe.',
|
||||||
statusText: 'Chờ tiếp nhận',
|
|
||||||
description: 'Thiết kế biệt thự 2 tầng phong cách luxury với hồ bơi và sân vườn. 5 phòng ngủ, 4 phòng tắm, phòng giải trí và garage 2 xe.',
|
|
||||||
contact: 'SĐT: 0923456789 | Email: duc.le@gmail.com',
|
|
||||||
createdDate: '28/10/2024',
|
createdDate: '28/10/2024',
|
||||||
files: ['mat-bang-dat.pdf', 'y-tuong-thiet-ke.jpg'],
|
files: ['mat-bang-dat.pdf', 'y-tuong-thiet-ke.jpg'],
|
||||||
designLink: null
|
designLink: null
|
||||||
@@ -615,10 +643,8 @@
|
|||||||
document.getElementById('project-name').textContent = request.name;
|
document.getElementById('project-name').textContent = request.name;
|
||||||
document.getElementById('project-area').textContent = request.area;
|
document.getElementById('project-area').textContent = request.area;
|
||||||
document.getElementById('project-style').textContent = request.style;
|
document.getElementById('project-style').textContent = request.style;
|
||||||
document.getElementById('project-budget').textContent = request.budget;
|
document.getElementById('project-budget').textContent = request.budget + ' VNĐ';
|
||||||
document.getElementById('project-status').textContent = request.statusText;
|
document.getElementById('project-notes').textContent = request.notes || 'Không có ghi chú đặc biệt';
|
||||||
document.getElementById('project-description').textContent = request.description;
|
|
||||||
document.getElementById('contact-info').textContent = request.contact;
|
|
||||||
|
|
||||||
// Update status badge
|
// Update status badge
|
||||||
const statusBadge = document.getElementById('status-badge');
|
const statusBadge = document.getElementById('status-badge');
|
||||||
@@ -633,8 +659,7 @@
|
|||||||
completionHighlight.style.display = 'none';
|
completionHighlight.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update files list
|
// Floor plan image - removed files list
|
||||||
updateFilesList(request.files);
|
|
||||||
|
|
||||||
// Update page title
|
// Update page title
|
||||||
document.title = `${request.id} - Chi tiết Yêu cầu Thiết kế`;
|
document.title = `${request.id} - Chi tiết Yêu cầu Thiết kế`;
|
||||||
@@ -643,37 +668,12 @@
|
|||||||
window.currentDesignLink = request.designLink;
|
window.currentDesignLink = request.designLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFilesList(files) {
|
function viewFloorPlan() {
|
||||||
const filesList = document.getElementById('files-list');
|
// In real app, open lightbox or full-screen image viewer
|
||||||
|
const img = document.querySelector('.floor-plan-image');
|
||||||
if (!files || files.length === 0) {
|
if (img && img.src) {
|
||||||
filesList.innerHTML = '<p style="color: #6b7280; font-style: italic;">Không có tài liệu đính kèm</p>';
|
window.open(img.src, '_blank');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filesList.innerHTML = files.map(fileName => {
|
|
||||||
const fileIcon = getFileIcon(fileName);
|
|
||||||
return `
|
|
||||||
<div class="file-item">
|
|
||||||
<div class="file-icon">
|
|
||||||
<i class="${fileIcon}"></i>
|
|
||||||
</div>
|
|
||||||
<div class="file-info">
|
|
||||||
<div class="file-name">${fileName}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFileIcon(fileName) {
|
|
||||||
const extension = fileName.toLowerCase().split('.').pop();
|
|
||||||
|
|
||||||
if (['jpg', 'jpeg', 'png', 'gif'].includes(extension)) return 'fas fa-image';
|
|
||||||
if (extension === 'pdf') return 'fas fa-file-pdf';
|
|
||||||
if (extension === 'dwg') return 'fas fa-drafting-compass';
|
|
||||||
if (['doc', 'docx'].includes(extension)) return 'fas fa-file-word';
|
|
||||||
return 'fas fa-file';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function viewDesign3D() {
|
function viewDesign3D() {
|
||||||
|
|||||||
@@ -356,6 +356,13 @@ class ApiConstants {
|
|||||||
static const String getDesignRequestDetail =
|
static const String getDesignRequestDetail =
|
||||||
'/building_material.building_material.api.design_request.get_detail';
|
'/building_material.building_material.api.design_request.get_detail';
|
||||||
|
|
||||||
|
/// Create a new design request (requires sid and csrf_token)
|
||||||
|
/// POST /api/method/building_material.building_material.api.design_request.create
|
||||||
|
/// Body: { "subject": "...", "area": "...", "region": "...", "desired_style": "...", "estimated_budget": "...", "detailed_requirements": "...", "dateline": "..." }
|
||||||
|
/// Returns: { "message": { "success": true, "data": { "name": "ISS-2025-00006" } } }
|
||||||
|
static const String createDesignRequest =
|
||||||
|
'/building_material.building_material.api.design_request.create';
|
||||||
|
|
||||||
/// Create new project (legacy endpoint - may be deprecated)
|
/// Create new project (legacy endpoint - may be deprecated)
|
||||||
/// POST /projects
|
/// POST /projects
|
||||||
static const String createProject = '/projects';
|
static const String createProject = '/projects';
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
/// Handles remote API calls for design requests.
|
/// Handles remote API calls for design requests.
|
||||||
library;
|
library;
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:worker/core/constants/api_constants.dart';
|
import 'package:worker/core/constants/api_constants.dart';
|
||||||
import 'package:worker/core/network/dio_client.dart';
|
import 'package:worker/core/network/dio_client.dart';
|
||||||
import 'package:worker/features/showrooms/data/models/design_request_model.dart';
|
import 'package:worker/features/showrooms/data/models/design_request_model.dart';
|
||||||
@@ -17,6 +18,24 @@ abstract class DesignRequestRemoteDataSource {
|
|||||||
|
|
||||||
/// Fetch detail of a design request by name
|
/// Fetch detail of a design request by name
|
||||||
Future<DesignRequestModel> getDesignRequestDetail(String name);
|
Future<DesignRequestModel> getDesignRequestDetail(String name);
|
||||||
|
|
||||||
|
/// Create a new design request
|
||||||
|
Future<String> createDesignRequest({
|
||||||
|
required String subject,
|
||||||
|
required String area,
|
||||||
|
required String region,
|
||||||
|
required String desiredStyle,
|
||||||
|
String? estimatedBudget,
|
||||||
|
required String detailedRequirements,
|
||||||
|
required String dateline,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Upload file attachment for a design request
|
||||||
|
Future<void> uploadDesignRequestFile({
|
||||||
|
required String requestId,
|
||||||
|
required String filePath,
|
||||||
|
required String fileName,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Design Request Remote Data Source Implementation
|
/// Design Request Remote Data Source Implementation
|
||||||
@@ -93,4 +112,94 @@ class DesignRequestRemoteDataSourceImpl implements DesignRequestRemoteDataSource
|
|||||||
throw Exception('Failed to get design request detail: $e');
|
throw Exception('Failed to get design request detail: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new design request
|
||||||
|
///
|
||||||
|
/// Calls: POST /api/method/building_material.building_material.api.design_request.create
|
||||||
|
/// Body: { "subject": "...", "area": "...", "region": "...", ... }
|
||||||
|
/// Returns: Created request ID (name)
|
||||||
|
@override
|
||||||
|
Future<String> createDesignRequest({
|
||||||
|
required String subject,
|
||||||
|
required String area,
|
||||||
|
required String region,
|
||||||
|
required String desiredStyle,
|
||||||
|
String? estimatedBudget,
|
||||||
|
required String detailedRequirements,
|
||||||
|
required String dateline,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await _dioClient.post<Map<String, dynamic>>(
|
||||||
|
'${ApiConstants.frappeApiMethod}${ApiConstants.createDesignRequest}',
|
||||||
|
data: {
|
||||||
|
'subject': subject,
|
||||||
|
'area': area,
|
||||||
|
'region': region,
|
||||||
|
'desired_style': desiredStyle,
|
||||||
|
if (estimatedBudget != null) 'estimated_budget': estimatedBudget,
|
||||||
|
'detailed_requirements': detailedRequirements,
|
||||||
|
'dateline': dateline,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final data = response.data;
|
||||||
|
if (data == null) {
|
||||||
|
throw Exception('No data received from createDesignRequest API');
|
||||||
|
}
|
||||||
|
|
||||||
|
// API returns: { "message": { "success": true, "data": { "name": "ISS-2025-00006" } } }
|
||||||
|
final message = data['message'] as Map<String, dynamic>?;
|
||||||
|
if (message == null) {
|
||||||
|
throw Exception('No message field in createDesignRequest response');
|
||||||
|
}
|
||||||
|
|
||||||
|
final success = message['success'] as bool? ?? false;
|
||||||
|
if (!success) {
|
||||||
|
throw Exception('Failed to create design request');
|
||||||
|
}
|
||||||
|
|
||||||
|
final requestData = message['data'] as Map<String, dynamic>?;
|
||||||
|
if (requestData == null) {
|
||||||
|
throw Exception('No data field in createDesignRequest response');
|
||||||
|
}
|
||||||
|
|
||||||
|
final name = requestData['name'] as String?;
|
||||||
|
if (name == null || name.isEmpty) {
|
||||||
|
throw Exception('No name returned from createDesignRequest');
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to create design request: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Upload file attachment for a design request
|
||||||
|
///
|
||||||
|
/// Calls: POST /api/method/upload_file
|
||||||
|
/// Form-data: { file, is_private, folder, doctype, docname, optimize }
|
||||||
|
@override
|
||||||
|
Future<void> uploadDesignRequestFile({
|
||||||
|
required String requestId,
|
||||||
|
required String filePath,
|
||||||
|
required String fileName,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final formData = FormData.fromMap({
|
||||||
|
'file': await MultipartFile.fromFile(filePath, filename: fileName),
|
||||||
|
'is_private': '0',
|
||||||
|
'folder': 'Home/Attachments',
|
||||||
|
'doctype': 'Issue',
|
||||||
|
'docname': requestId,
|
||||||
|
'optimize': 'true',
|
||||||
|
});
|
||||||
|
|
||||||
|
await _dioClient.post<Map<String, dynamic>>(
|
||||||
|
'${ApiConstants.frappeApiMethod}${ApiConstants.uploadFile}',
|
||||||
|
data: formData,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to upload file: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,4 +38,46 @@ class DesignRequestRepositoryImpl implements DesignRequestRepository {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> createDesignRequest({
|
||||||
|
required String subject,
|
||||||
|
required String area,
|
||||||
|
required String region,
|
||||||
|
required String desiredStyle,
|
||||||
|
String? estimatedBudget,
|
||||||
|
required String detailedRequirements,
|
||||||
|
required String dateline,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
return await _remoteDataSource.createDesignRequest(
|
||||||
|
subject: subject,
|
||||||
|
area: area,
|
||||||
|
region: region,
|
||||||
|
desiredStyle: desiredStyle,
|
||||||
|
estimatedBudget: estimatedBudget,
|
||||||
|
detailedRequirements: detailedRequirements,
|
||||||
|
dateline: dateline,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> uploadDesignRequestFile({
|
||||||
|
required String requestId,
|
||||||
|
required String filePath,
|
||||||
|
required String fileName,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
await _remoteDataSource.uploadDesignRequestFile(
|
||||||
|
requestId: requestId,
|
||||||
|
filePath: filePath,
|
||||||
|
fileName: fileName,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,4 +23,24 @@ abstract class DesignRequestRepository {
|
|||||||
///
|
///
|
||||||
/// Returns full design request detail with files_list.
|
/// Returns full design request detail with files_list.
|
||||||
Future<DesignRequest> getDesignRequestDetail(String name);
|
Future<DesignRequest> getDesignRequestDetail(String name);
|
||||||
|
|
||||||
|
/// Create a new design request
|
||||||
|
///
|
||||||
|
/// Returns created request ID (name).
|
||||||
|
Future<String> createDesignRequest({
|
||||||
|
required String subject,
|
||||||
|
required String area,
|
||||||
|
required String region,
|
||||||
|
required String desiredStyle,
|
||||||
|
String? estimatedBudget,
|
||||||
|
required String detailedRequirements,
|
||||||
|
required String dateline,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Upload file attachment for a design request
|
||||||
|
Future<void> uploadDesignRequestFile({
|
||||||
|
required String requestId,
|
||||||
|
required String filePath,
|
||||||
|
required String fileName,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,17 @@
|
|||||||
/// Form to create a new design request following html/design-request-create.html.
|
/// Form to create a new design request following html/design-request-create.html.
|
||||||
library;
|
library;
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:worker/core/constants/ui_constants.dart';
|
import 'package:worker/core/constants/ui_constants.dart';
|
||||||
import 'package:worker/core/theme/colors.dart';
|
import 'package:worker/core/theme/colors.dart';
|
||||||
|
import 'package:worker/features/showrooms/presentation/providers/design_request_provider.dart';
|
||||||
|
|
||||||
/// Design Request Create Page
|
/// Design Request Create Page
|
||||||
///
|
///
|
||||||
@@ -28,11 +32,14 @@ class DesignRequestCreatePage extends HookConsumerWidget {
|
|||||||
final areaController = useTextEditingController();
|
final areaController = useTextEditingController();
|
||||||
final locationController = useTextEditingController();
|
final locationController = useTextEditingController();
|
||||||
final notesController = useTextEditingController();
|
final notesController = useTextEditingController();
|
||||||
|
final datelineController = useTextEditingController();
|
||||||
|
|
||||||
final selectedStyle = useState<String>('');
|
final selectedStyle = useState<String>('');
|
||||||
final selectedBudget = useState<String>('');
|
final selectedBudget = useState<String>('');
|
||||||
|
final selectedDateline = useState<DateTime?>(null);
|
||||||
final selectedFiles = useState<List<PlatformFile>>([]);
|
final selectedFiles = useState<List<PlatformFile>>([]);
|
||||||
final isSubmitting = useState(false);
|
final isSubmitting = useState(false);
|
||||||
|
final submissionStatus = useState<String>('');
|
||||||
|
|
||||||
Future<void> pickFiles() async {
|
Future<void> pickFiles() async {
|
||||||
try {
|
try {
|
||||||
@@ -80,14 +87,115 @@ class DesignRequestCreatePage extends HookConsumerWidget {
|
|||||||
selectedFiles.value = files;
|
selectedFiles.value = files;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> submitForm() async {
|
Future<void> pickDateline() async {
|
||||||
if (formKey.currentState?.validate() ?? false) {
|
final picked = await showDatePicker(
|
||||||
isSubmitting.value = true;
|
context: context,
|
||||||
|
initialDate: selectedDateline.value ?? DateTime.now().add(const Duration(days: 30)),
|
||||||
|
firstDate: DateTime.now(),
|
||||||
|
lastDate: DateTime.now().add(const Duration(days: 365)),
|
||||||
|
locale: const Locale('vi', 'VN'),
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
selectedDateline.value = picked;
|
||||||
|
datelineController.text = DateFormat('dd/MM/yyyy').format(picked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Simulate API call
|
/// Get style display text for API
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
String getStyleText(String value) {
|
||||||
|
switch (value) {
|
||||||
|
case 'hien-dai':
|
||||||
|
return 'Hiện đại';
|
||||||
|
case 'toi-gian':
|
||||||
|
return 'Tối giản';
|
||||||
|
case 'co-dien':
|
||||||
|
return 'Cổ điển';
|
||||||
|
case 'scandinavian':
|
||||||
|
return 'Scandinavian';
|
||||||
|
case 'industrial':
|
||||||
|
return 'Industrial';
|
||||||
|
case 'tropical':
|
||||||
|
return 'Tropical';
|
||||||
|
case 'luxury':
|
||||||
|
return 'Luxury';
|
||||||
|
case 'khac':
|
||||||
|
return 'Khác';
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get budget display text for API
|
||||||
|
String getBudgetText(String value) {
|
||||||
|
switch (value) {
|
||||||
|
case 'duoi-100tr':
|
||||||
|
return 'Dưới 100 triệu';
|
||||||
|
case '100-300tr':
|
||||||
|
return '100 - 300 triệu';
|
||||||
|
case '300-500tr':
|
||||||
|
return '300 - 500 triệu';
|
||||||
|
case '500tr-1ty':
|
||||||
|
return '500 triệu - 1 tỷ';
|
||||||
|
case 'tren-1ty':
|
||||||
|
return 'Trên 1 tỷ';
|
||||||
|
case 'trao-doi':
|
||||||
|
return 'Trao đổi trực tiếp';
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> submitForm() async {
|
||||||
|
if (!(formKey.currentState?.validate() ?? false)) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Vui lòng kiểm tra lại thông tin'),
|
||||||
|
backgroundColor: AppColors.danger,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSubmitting.value = true;
|
||||||
|
submissionStatus.value = 'Đang tạo yêu cầu...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get repository
|
||||||
|
final repository = await ref.read(designRequestRepositoryProvider.future);
|
||||||
|
|
||||||
|
// Step 1: Create design request
|
||||||
|
final requestId = await repository.createDesignRequest(
|
||||||
|
subject: projectNameController.text.trim(),
|
||||||
|
area: areaController.text.trim(),
|
||||||
|
region: locationController.text.trim(),
|
||||||
|
desiredStyle: getStyleText(selectedStyle.value),
|
||||||
|
estimatedBudget: selectedBudget.value.isNotEmpty
|
||||||
|
? getBudgetText(selectedBudget.value)
|
||||||
|
: null,
|
||||||
|
detailedRequirements: notesController.text.trim(),
|
||||||
|
dateline: DateFormat('yyyy-MM-dd').format(selectedDateline.value!),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 2: Upload files if any
|
||||||
|
if (selectedFiles.value.isNotEmpty) {
|
||||||
|
submissionStatus.value = 'Đang tải lên tệp đính kèm...';
|
||||||
|
|
||||||
|
for (int i = 0; i < selectedFiles.value.length; i++) {
|
||||||
|
final file = selectedFiles.value[i];
|
||||||
|
submissionStatus.value = 'Đang tải lên tệp ${i + 1}/${selectedFiles.value.length}...';
|
||||||
|
|
||||||
|
if (file.path != null) {
|
||||||
|
await repository.uploadDesignRequestFile(
|
||||||
|
requestId: requestId,
|
||||||
|
filePath: file.path!,
|
||||||
|
fileName: file.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isSubmitting.value = false;
|
isSubmitting.value = false;
|
||||||
|
submissionStatus.value = '';
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@@ -98,19 +206,23 @@ class DesignRequestCreatePage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Navigate back
|
// Navigate back
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
context.pop();
|
||||||
if (context.mounted) {
|
|
||||||
context.pop();
|
// Refresh the list (fire and forget)
|
||||||
}
|
unawaited(ref.read(designRequestsListProvider.notifier).refresh());
|
||||||
});
|
}
|
||||||
|
} catch (e) {
|
||||||
|
isSubmitting.value = false;
|
||||||
|
submissionStatus.value = '';
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Lỗi: ${e.toString().replaceAll('Exception: ', '')}'),
|
||||||
|
backgroundColor: AppColors.danger,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('Vui lòng kiểm tra lại thông tin'),
|
|
||||||
backgroundColor: AppColors.danger,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,33 +252,10 @@ class DesignRequestCreatePage extends HookConsumerWidget {
|
|||||||
key: formKey,
|
key: formKey,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Progress Steps
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
_ProgressStep(number: 1, isActive: true),
|
|
||||||
Container(
|
|
||||||
width: 16,
|
|
||||||
height: 2,
|
|
||||||
color: AppColors.grey100,
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
),
|
|
||||||
_ProgressStep(number: 2),
|
|
||||||
Container(
|
|
||||||
width: 16,
|
|
||||||
height: 2,
|
|
||||||
color: AppColors.grey100,
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
),
|
|
||||||
_ProgressStep(number: 3),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
|
|
||||||
// Basic Information Card
|
// Basic Information Card
|
||||||
Card(
|
Card(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
@@ -428,6 +517,72 @@ class DesignRequestCreatePage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Dateline
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
RichText(
|
||||||
|
text: const TextSpan(
|
||||||
|
text: 'Thời hạn mong muốn',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppColors.grey900,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: ' *',
|
||||||
|
style: TextStyle(color: AppColors.danger),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextFormField(
|
||||||
|
controller: datelineController,
|
||||||
|
readOnly: true,
|
||||||
|
onTap: pickDateline,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Chọn ngày',
|
||||||
|
suffixIcon: const Icon(Icons.calendar_today, size: 20),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: const BorderSide(
|
||||||
|
color: AppColors.grey100,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: const BorderSide(
|
||||||
|
color: AppColors.grey100,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: const BorderSide(
|
||||||
|
color: AppColors.primaryBlue,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Vui lòng chọn thời hạn';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -438,6 +593,7 @@ class DesignRequestCreatePage extends HookConsumerWidget {
|
|||||||
// Detailed Requirements Card
|
// Detailed Requirements Card
|
||||||
Card(
|
Card(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
@@ -536,6 +692,8 @@ class DesignRequestCreatePage extends HookConsumerWidget {
|
|||||||
// File Upload Card
|
// File Upload Card
|
||||||
Card(
|
Card(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
@@ -634,8 +792,8 @@ class DesignRequestCreatePage extends HookConsumerWidget {
|
|||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: AppColors.primaryBlue,
|
backgroundColor: AppColors.primaryBlue,
|
||||||
foregroundColor: AppColors.white,
|
foregroundColor: AppColors.white,
|
||||||
disabledBackgroundColor: AppColors.grey100,
|
disabledBackgroundColor: AppColors.primaryBlue.withValues(alpha: 0.7),
|
||||||
disabledForegroundColor: AppColors.grey500,
|
disabledForegroundColor: AppColors.white,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
@@ -643,13 +801,28 @@ class DesignRequestCreatePage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: isSubmitting.value
|
child: isSubmitting.value
|
||||||
? const SizedBox(
|
? Row(
|
||||||
height: 20,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
width: 20,
|
children: [
|
||||||
child: CircularProgressIndicator(
|
const SizedBox(
|
||||||
strokeWidth: 2,
|
height: 20,
|
||||||
color: AppColors.white,
|
width: 20,
|
||||||
),
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: AppColors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
submissionStatus.value.isNotEmpty
|
||||||
|
? submissionStatus.value
|
||||||
|
: 'Đang xử lý...',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
: const Row(
|
: const Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -678,45 +851,6 @@ class DesignRequestCreatePage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Progress Step Widget
|
/// Progress Step Widget
|
||||||
class _ProgressStep extends StatelessWidget {
|
|
||||||
final int number;
|
|
||||||
final bool isActive;
|
|
||||||
final bool isCompleted;
|
|
||||||
|
|
||||||
const _ProgressStep({
|
|
||||||
required this.number,
|
|
||||||
this.isActive = false,
|
|
||||||
this.isCompleted = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
color: isCompleted
|
|
||||||
? AppColors.success
|
|
||||||
: isActive
|
|
||||||
? AppColors.primaryBlue
|
|
||||||
: AppColors.grey100,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
'$number',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: isActive || isCompleted
|
|
||||||
? AppColors.white
|
|
||||||
: AppColors.grey500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Form Field Widget
|
/// Form Field Widget
|
||||||
class _FormField extends StatelessWidget {
|
class _FormField extends StatelessWidget {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/// Page: Design Request Detail Page
|
/// Page: Design Request Detail Page
|
||||||
///
|
///
|
||||||
/// Displays design request details from API.
|
/// Displays design request details from API following html/design-request-detail.html.
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:worker/core/constants/ui_constants.dart';
|
import 'package:worker/core/constants/ui_constants.dart';
|
||||||
import 'package:worker/core/router/app_router.dart';
|
import 'package:worker/core/router/app_router.dart';
|
||||||
import 'package:worker/core/theme/colors.dart';
|
import 'package:worker/core/theme/colors.dart';
|
||||||
@@ -19,7 +20,8 @@ import 'package:worker/features/showrooms/presentation/providers/design_request_
|
|||||||
///
|
///
|
||||||
/// Shows complete details of a design request including:
|
/// Shows complete details of a design request including:
|
||||||
/// - Request header with ID, date, and status
|
/// - Request header with ID, date, and status
|
||||||
/// - Subject and description
|
/// - Design information (subject, description)
|
||||||
|
/// - Completion highlight for completed requests
|
||||||
/// - Attached files/images
|
/// - Attached files/images
|
||||||
class DesignRequestDetailPage extends ConsumerWidget {
|
class DesignRequestDetailPage extends ConsumerWidget {
|
||||||
const DesignRequestDetailPage({required this.requestId, super.key});
|
const DesignRequestDetailPage({required this.requestId, super.key});
|
||||||
@@ -29,7 +31,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
|
|||||||
Color _getStatusColor(DesignRequestStatus status) {
|
Color _getStatusColor(DesignRequestStatus status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case DesignRequestStatus.pending:
|
case DesignRequestStatus.pending:
|
||||||
return const Color(0xFFffc107);
|
return const Color(0xFFd97706);
|
||||||
case DesignRequestStatus.designing:
|
case DesignRequestStatus.designing:
|
||||||
return const Color(0xFF3730a3);
|
return const Color(0xFF3730a3);
|
||||||
case DesignRequestStatus.completed:
|
case DesignRequestStatus.completed:
|
||||||
@@ -117,6 +119,36 @@ class DesignRequestDetailPage extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _viewDesign3D(BuildContext context) async {
|
||||||
|
// TODO: Replace with actual design link from API when available
|
||||||
|
const designUrl = 'https://example.com/3d-design';
|
||||||
|
|
||||||
|
try {
|
||||||
|
final uri = Uri.parse(designUrl);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||||
|
} else {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Link thiết kế 3D chưa sẵn sàng'),
|
||||||
|
backgroundColor: AppColors.warning,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Lỗi khi mở link: $e'),
|
||||||
|
backgroundColor: AppColors.danger,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _showImageViewer(
|
void _showImageViewer(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
List<ProjectFile> files,
|
List<ProjectFile> files,
|
||||||
@@ -174,7 +206,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
|
|||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Request Header Card
|
// Request Header & Design Info Card
|
||||||
Card(
|
Card(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
@@ -185,46 +217,97 @@ class DesignRequestDetailPage extends ConsumerWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Request ID
|
// Header Section (centered)
|
||||||
Text(
|
Center(
|
||||||
'#${request.id}',
|
child: Column(
|
||||||
style: const TextStyle(
|
children: [
|
||||||
fontSize: 24,
|
// Request ID
|
||||||
fontWeight: FontWeight.w700,
|
Text(
|
||||||
color: AppColors.grey900,
|
'#${request.id}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: AppColors.grey900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// Date
|
||||||
|
if (request.dateline != null)
|
||||||
|
Text(
|
||||||
|
'Ngày gửi: ${request.dateline}',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Status Badge
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: _getStatusBackgroundColor(request.status),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
request.statusText.toUpperCase(),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: _getStatusColor(request.status),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
|
||||||
if (request.dateline != null)
|
const SizedBox(height: 24),
|
||||||
Text(
|
const Divider(height: 1, color: AppColors.grey100),
|
||||||
'Deadline: ${request.dateline}',
|
const SizedBox(height: 24),
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14,
|
// Design Information Section
|
||||||
color: AppColors.grey500,
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.info_outline,
|
||||||
|
color: AppColors.primaryBlue,
|
||||||
|
size: 20,
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
|
const Text(
|
||||||
|
'Thông tin thiết kế',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: AppColors.grey900,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Status Badge
|
// Description List
|
||||||
Container(
|
_DescriptionItem(
|
||||||
padding: const EdgeInsets.symmetric(
|
label: 'Tên công trình:',
|
||||||
horizontal: 16,
|
value: request.subject,
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: _getStatusBackgroundColor(request.status),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
request.statusText.toUpperCase(),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: _getStatusColor(request.status),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
|
if (request.plainDescription.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_DescriptionItem(
|
||||||
|
label: 'Mô tả chi tiết:',
|
||||||
|
value: request.plainDescription,
|
||||||
|
isMultiLine: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -251,23 +334,43 @@ class DesignRequestDetailPage extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(20),
|
||||||
child: const Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
const Text(
|
||||||
'🎉 Yêu cầu đã hoàn thành!',
|
'🎉 Thiết kế đã hoàn thành!',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
color: Color(0xFF065f46),
|
color: Color(0xFF065f46),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
const Text(
|
||||||
'Thiết kế của bạn đã sẵn sàng',
|
'Thiết kế 3D của bạn đã sẵn sàng để xem',
|
||||||
style: TextStyle(color: Color(0xFF065f46)),
|
style: TextStyle(color: Color(0xFF065f46)),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () => _viewDesign3D(context),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: const Color(0xFF10b981),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.view_in_ar, size: 20),
|
||||||
|
label: const Text(
|
||||||
|
'Xem Link Thiết kế 3D',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -294,7 +397,7 @@ class DesignRequestDetailPage extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(20),
|
||||||
child: const Column(
|
child: const Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
@@ -318,63 +421,46 @@ class DesignRequestDetailPage extends ConsumerWidget {
|
|||||||
|
|
||||||
if (request.isRejected) const SizedBox(height: 20),
|
if (request.isRejected) const SizedBox(height: 20),
|
||||||
|
|
||||||
// Project Details Card
|
// Attached Files Card
|
||||||
Card(
|
if (request.filesList.isNotEmpty)
|
||||||
elevation: 2,
|
Card(
|
||||||
margin: EdgeInsets.zero,
|
elevation: 2,
|
||||||
shape: RoundedRectangleBorder(
|
margin: EdgeInsets.zero,
|
||||||
borderRadius: BorderRadius.circular(12),
|
shape: RoundedRectangleBorder(
|
||||||
),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: Padding(
|
),
|
||||||
padding: const EdgeInsets.all(24),
|
child: Container(
|
||||||
child: Column(
|
width: double.infinity,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
padding: const EdgeInsets.all(24),
|
||||||
children: [
|
child: Column(
|
||||||
// Subject
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
_SectionHeader(icon: Icons.info, title: 'Tiêu đề'),
|
children: [
|
||||||
const SizedBox(height: 12),
|
Row(
|
||||||
Text(
|
children: [
|
||||||
request.subject,
|
Icon(
|
||||||
style: const TextStyle(
|
Icons.attach_file,
|
||||||
fontSize: 16,
|
color: AppColors.primaryBlue,
|
||||||
fontWeight: FontWeight.w600,
|
size: 20,
|
||||||
color: AppColors.grey900,
|
),
|
||||||
height: 1.6,
|
const SizedBox(width: 8),
|
||||||
),
|
const Text(
|
||||||
),
|
'Tài liệu đính kèm',
|
||||||
|
style: TextStyle(
|
||||||
if (request.plainDescription.isNotEmpty) ...[
|
fontSize: 18,
|
||||||
const SizedBox(height: 24),
|
fontWeight: FontWeight.w700,
|
||||||
|
color: AppColors.grey900,
|
||||||
// Description
|
),
|
||||||
_SectionHeader(icon: Icons.edit, title: 'Mô tả yêu cầu'),
|
),
|
||||||
const SizedBox(height: 12),
|
],
|
||||||
Text(
|
|
||||||
request.plainDescription,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: AppColors.grey500,
|
|
||||||
height: 1.6,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
|
|
||||||
// Files
|
|
||||||
if (request.filesList.isNotEmpty) ...[
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
_SectionHeader(
|
|
||||||
icon: Icons.attach_file,
|
|
||||||
title: 'Tài liệu đính kèm (${request.filesList.length})',
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildFilesSection(context, request.filesList),
|
_buildFilesSection(context, request.filesList),
|
||||||
],
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: 20),
|
if (request.filesList.isNotEmpty) const SizedBox(height: 20),
|
||||||
|
|
||||||
// Action Button
|
// Action Button
|
||||||
SizedBox(
|
SizedBox(
|
||||||
@@ -389,9 +475,9 @@ class DesignRequestDetailPage extends ConsumerWidget {
|
|||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.chat_bubble),
|
icon: const Icon(Icons.chat_bubble_outline),
|
||||||
label: const Text(
|
label: const Text(
|
||||||
'Liên hệ hỗ trợ',
|
'Liên hệ',
|
||||||
style: TextStyle(fontWeight: FontWeight.w600),
|
style: TextStyle(fontWeight: FontWeight.w600),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -487,40 +573,99 @@ class DesignRequestDetailPage extends ConsumerWidget {
|
|||||||
if (otherFiles.isNotEmpty) const SizedBox(height: 16),
|
if (otherFiles.isNotEmpty) const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
|
|
||||||
// Other Files
|
// Other Files as file items
|
||||||
...otherFiles.map(
|
...otherFiles.map(
|
||||||
(file) => _FileItem(
|
(file) => _FileItem(
|
||||||
fileUrl: file.fileUrl,
|
fileUrl: file.fileUrl,
|
||||||
icon: _getFileIcon(file.fileUrl),
|
icon: _getFileIcon(file.fileUrl),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Show image files as file items too (for download/naming)
|
||||||
|
if (images.isNotEmpty && otherFiles.isEmpty)
|
||||||
|
...images.map(
|
||||||
|
(file) => _FileItem(
|
||||||
|
fileUrl: file.fileUrl,
|
||||||
|
icon: Icons.image,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Section Header Widget
|
/// Description Item Widget
|
||||||
class _SectionHeader extends StatelessWidget {
|
class _DescriptionItem extends StatelessWidget {
|
||||||
const _SectionHeader({required this.icon, required this.title});
|
const _DescriptionItem({
|
||||||
|
required this.label,
|
||||||
|
required this.value,
|
||||||
|
this.isMultiLine = false,
|
||||||
|
});
|
||||||
|
|
||||||
final IconData icon;
|
final String label;
|
||||||
final String title;
|
final String value;
|
||||||
|
final bool isMultiLine;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
if (isMultiLine) {
|
||||||
children: [
|
return Column(
|
||||||
Icon(icon, color: AppColors.primaryBlue, size: 20),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
const SizedBox(width: 8),
|
children: [
|
||||||
Text(
|
Text(
|
||||||
title,
|
label,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.w700,
|
color: AppColors.grey500,
|
||||||
color: AppColors.grey900,
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 4),
|
||||||
],
|
Text(
|
||||||
|
value,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
color: AppColors.grey900,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
height: 1.6,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.only(bottom: 12),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(bottom: BorderSide(color: AppColors.grey100)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 120,
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: AppColors.grey500,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
value,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 15,
|
||||||
|
color: AppColors.grey900,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
height: 1.6,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -543,7 +688,7 @@ class _FileItem extends StatelessWidget {
|
|||||||
margin: const EdgeInsets.only(bottom: 8),
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.grey50,
|
color: AppColors.grey100,
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -555,7 +700,7 @@ class _FileItem extends StatelessWidget {
|
|||||||
color: AppColors.primaryBlue,
|
color: AppColors.primaryBlue,
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
),
|
),
|
||||||
child: Icon(icon, color: Colors.white, size: 16),
|
child: Icon(icon, color: Colors.white, size: 14),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -577,14 +722,14 @@ class _FileItem extends StatelessWidget {
|
|||||||
|
|
||||||
/// Image Viewer Dialog with Swipe Navigation
|
/// Image Viewer Dialog with Swipe Navigation
|
||||||
class _ImageViewerDialog extends StatefulWidget {
|
class _ImageViewerDialog extends StatefulWidget {
|
||||||
final List<ProjectFile> images;
|
|
||||||
final int initialIndex;
|
|
||||||
|
|
||||||
const _ImageViewerDialog({
|
const _ImageViewerDialog({
|
||||||
required this.images,
|
required this.images,
|
||||||
required this.initialIndex,
|
required this.initialIndex,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final List<ProjectFile> images;
|
||||||
|
final int initialIndex;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_ImageViewerDialog> createState() => _ImageViewerDialogState();
|
State<_ImageViewerDialog> createState() => _ImageViewerDialogState();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user