Sửa lỗi hiển thị bảng liệt kê chi phí

This commit is contained in:
2026-06-16 10:28:33 +07:00
parent baa83aad8a
commit 3e1a5db1a6
5 changed files with 79 additions and 40 deletions
+12
View File
@@ -176,6 +176,12 @@ let PublicTourController = class PublicTourController {
legs: { legs: {
orderBy: { sequence: 'asc' }, orderBy: { sequence: 'asc' },
include: { include: {
expenses: {
include: {
location: { select: { name: true, plannedStart: true } },
paidBy: { select: { name: true } }
}
},
locations: { locations: {
orderBy: { plannedStart: 'asc' }, orderBy: { plannedStart: 'asc' },
include: { _count: { select: { comments: true } } } include: { _count: { select: { comments: true } } }
@@ -432,6 +438,12 @@ let TourController = class TourController {
legs: { legs: {
orderBy: { sequence: 'asc' }, orderBy: { sequence: 'asc' },
include: { include: {
expenses: {
include: {
location: { select: { name: true, plannedStart: true } },
paidBy: { select: { name: true } }
}
},
locations: { locations: {
orderBy: { plannedStart: 'asc' }, orderBy: { plannedStart: 'asc' },
include: { _count: { select: { comments: true } } } include: { _count: { select: { comments: true } } }
+1 -1
View File
File diff suppressed because one or more lines are too long
+12
View File
@@ -123,6 +123,12 @@ class PublicTourController {
legs: { legs: {
orderBy: { sequence: 'asc' }, orderBy: { sequence: 'asc' },
include: { include: {
expenses: {
include: {
location: { select: { name: true, plannedStart: true } },
paidBy: { select: { name: true } }
}
},
locations: { locations: {
orderBy: { plannedStart: 'asc' }, orderBy: { plannedStart: 'asc' },
include: { _count: { select: { comments: true } } } include: { _count: { select: { comments: true } } }
@@ -427,6 +433,12 @@ class TourController {
legs: { legs: {
orderBy: { sequence: 'asc' }, orderBy: { sequence: 'asc' },
include: { include: {
expenses: {
include: {
location: { select: { name: true, plannedStart: true } },
paidBy: { select: { name: true } }
}
},
locations: { locations: {
orderBy: { plannedStart: 'asc' }, orderBy: { plannedStart: 'asc' },
include: { _count: { select: { comments: true } } } include: { _count: { select: { comments: true } } }
+8 -3
View File
@@ -96,7 +96,7 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
type: editingLocation.type || 'VISIT', type: editingLocation.type || 'VISIT',
legId: editingLocation.legId || '', legId: editingLocation.legId || '',
note: editingLocation.note || '', note: editingLocation.note || '',
expenseAmount: expense?.amount?.toString() || '', expenseAmount: expense?.amount ? Number(expense.amount).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".") : '',
expenseCategory: expense?.category || 'OTHER', expenseCategory: expense?.category || 'OTHER',
expenseDescription: expense?.description || '', expenseDescription: expense?.description || '',
expenseNote: expense?.note || '', expenseNote: expense?.note || '',
@@ -155,6 +155,7 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
try { try {
const payload: any = { const payload: any = {
...formData, ...formData,
expenseAmount: formData.expenseAmount.replace(/\./g, ''), // Loại bỏ dấu chấm trước khi gửi
legId: currentLegId, legId: currentLegId,
latitude: parseFloat(formData.latitude as any), latitude: parseFloat(formData.latitude as any),
longitude: parseFloat(formData.longitude as any), longitude: parseFloat(formData.longitude as any),
@@ -220,9 +221,13 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div> <div>
<label className="block text-xs font-bold text-gray-600 mb-1">Số tiền (VNĐ)</label> <label className="block text-xs font-bold text-gray-600 mb-1">Số tiền (VNĐ)</label>
<input type="number" className="w-full px-3 py-2 bg-white border border-gray-200 rounded-xl outline-none text-sm" <input type="text" className="w-full px-3 py-2 bg-white border border-gray-200 rounded-xl outline-none text-sm"
placeholder="0" placeholder="0"
value={formData.expenseAmount} onChange={e => setFormData({...formData, expenseAmount: e.target.value})} /> value={formData.expenseAmount} onChange={e => {
const rawValue = e.target.value.replace(/\D/g, ""); // Chỉ lấy số
const formattedValue = rawValue.replace(/\B(?=(\d{3})+(?!\d))/g, "."); // Thêm dấu chấm
setFormData({...formData, expenseAmount: formattedValue});
}} />
</div> </div>
<div> <div>
<label className="block text-xs font-bold text-gray-600 mb-1">Loại dịch vụ</label> <label className="block text-xs font-bold text-gray-600 mb-1">Loại dịch vụ</label>
+46 -36
View File
@@ -56,16 +56,24 @@ export const ExpenseManager = () => {
const avgPerLeg = legs.length > 0 ? totalAmount / legs.length : 0; const avgPerLeg = legs.length > 0 ? totalAmount / legs.length : 0;
// Danh sách phẳng tất cả chi phí để hiển thị bảng // Danh sách phẳng tất cả chi phí để hiển thị bảng
const flatExpenses = (legs || []).flatMap(leg => const flatExpenses: any[] = [];
(leg.expenses || []).map((exp: any) => ({ [...(legs || [])].sort((a, b) => a.sequence - b.sequence).forEach((leg, legIdx) => {
const legExpenses = (leg.expenses || []).map((exp: any) => ({
...exp, ...exp,
legSequence: leg.sequence, legSequence: leg.sequence,
// Lấy ngày của Location nếu có, nếu không lấy ngày của Leg legDisplayIndex: legIdx + 1, // Số thứ tự chặng liên tục (1, 2, 3...)
date: exp.location?.plannedStart || leg.startDate || null date: exp.location?.plannedStart || leg.startDate || null
})) })).sort((a: any, b: any) => {
).sort((a, b) => { if (!a.date || !b.date) return 0;
if (!a.date || !b.date) return 0; return new Date(a.date).getTime() - new Date(b.date).getTime();
return new Date(a.date).getTime() - new Date(b.date).getTime(); });
legExpenses.forEach((exp: any, idx: number) => {
flatExpenses.push({
...exp,
isFirstInLeg: idx === 0
});
});
}); });
const childRateFactor = 1 - (Number(discount) / 100); const childRateFactor = 1 - (Number(discount) / 100);
@@ -126,10 +134,10 @@ export const ExpenseManager = () => {
// Cấu hình bảng dữ liệu // Cấu hình bảng dữ liệu
const tableColumn = ["STT", "Ngày giờ", "Chặng", "Dịch vụ", "Số tiền (VNĐ)", "Người chi"]; const tableColumn = ["STT", "Ngày giờ", "Chặng", "Dịch vụ", "Số tiền (VNĐ)", "Người chi"];
const tableRows = stats.list.map((exp: any, index: number) => [ const tableRows = stats.list.map((exp: any) => [
index + 1, exp.isFirstInLeg ? exp.legSequence : '',
exp.date ? format(new Date(exp.date), 'dd/MM HH:mm') : '--/--', exp.date ? format(new Date(exp.date), 'd/M - p') : '--/--',
`Chặng ${exp.legSequence}`, exp.isFirstInLeg ? `Chặng ${exp.legSequence}` : '',
exp.description || 'Không có mô tả', exp.description || 'Không có mô tả',
Number(exp.amount).toLocaleString(), Number(exp.amount).toLocaleString(),
exp.paidBy?.name || 'Chưa rõ' exp.paidBy?.name || 'Chưa rõ'
@@ -305,22 +313,35 @@ export const ExpenseManager = () => {
</thead> </thead>
<tbody className="divide-y divide-gray-50"> <tbody className="divide-y divide-gray-50">
{stats.list.length > 0 ? ( {stats.list.length > 0 ? (
stats.list.map((exp: any, index: number) => ( stats.list.map((exp: any) => (
<tr key={exp.id} className="hover:bg-blue-50/20 transition-colors text-sm"> <tr key={exp.id} className={`hover:bg-blue-50/20 transition-colors text-sm ${!exp.isFirstInLeg ? 'bg-gray-50/10' : ''}`}>
<td className="px-4 py-4 text-center font-bold text-gray-400">{index + 1}</td> <td className="px-4 py-4 text-center font-black text-gray-400">
{exp.isFirstInLeg ? exp.legDisplayIndex : ''}
</td>
<td className="px-4 py-4"> <td className="px-4 py-4">
<div className="flex items-center gap-1.5 text-gray-600 font-medium"> <div className="flex items-center gap-1.5 text-gray-600 font-medium">
<Calendar className="w-3.5 h-3.5 opacity-40" /> <Calendar className="w-3.5 h-3.5 opacity-40" />
{exp.date ? format(new Date(exp.date), 'dd/MM HH:mm') : '--/--'} {exp.date ? format(new Date(exp.date), 'd/M - p') : '--/--'}
</div> </div>
</td> </td>
<td className="px-4 py-4 whitespace-nowrap"> <td className="px-4 py-4 whitespace-nowrap">
<span className="px-2 py-1 bg-blue-50 text-blue-600 rounded-lg font-bold text-[10px]"> {exp.isFirstInLeg ? (
Chặng {exp.legSequence} <span className="px-2 py-1 bg-blue-50 text-blue-600 rounded-lg font-bold text-[10px]">
</span> Chặng {exp.legSequence}
</span>
) : (
<div className="ml-6 border-l-2 border-blue-100/50 h-4" />
)}
</td> </td>
<td className="px-4 py-4 font-semibold text-gray-800"> <td className="px-4 py-4 font-semibold text-gray-800">
{exp.description || 'Không có mô tả'} {exp.location?.name ? (
<div className="flex flex-col">
<span className="text-gray-900">{exp.description || 'Chi phí dịch vụ'}</span>
<span className="text-[10px] text-blue-500 font-bold uppercase tracking-tighter">@{exp.location.name}</span>
</div>
) : (
exp.description || 'Không có mô tả'
)}
</td> </td>
<td className="px-4 py-4 text-right font-black text-blue-600"> <td className="px-4 py-4 text-right font-black text-blue-600">
{Number(exp.amount).toLocaleString()}đ {Number(exp.amount).toLocaleString()}đ
@@ -337,26 +358,15 @@ export const ExpenseManager = () => {
</tr> </tr>
)) ))
) : ( ) : (
// Khung bảng mẫu khi chưa có dữ liệu (Placeholder)
[1, 2, 3].map((i) => ( [1, 2, 3].map((i) => (
<tr key={`sample-${i}`} className="opacity-30 grayscale pointer-events-none select-none text-sm"> <tr key={`sample-${i}`} className="opacity-30 grayscale pointer-events-none select-none text-sm">
<td className="px-4 py-4 text-center font-bold text-gray-300">{i}</td> <td className="px-4 py-4 text-center font-bold text-gray-300">{i}</td>
<td className="px-4 py-4"> <td className="px-4 py-4 text-gray-300">--/-- --:--</td>
<div className="flex items-center gap-1.5 text-gray-300 font-medium"> <td className="px-4 py-4 text-gray-300">Chặng -</td>
<Calendar className="w-3.5 h-3.5 opacity-20" /> --/-- --:-- <td className="px-4 py-4 text-gray-300 italic">Chưa dữ liệu...</td>
</div> <td className="px-4 py-4 text-right text-gray-300">0đ</td>
</td> <td className="px-4 py-4 text-gray-300">Chưa </td>
<td className="px-4 py-4 whitespace-nowrap"> <td className="px-4 py-4 text-gray-200">-</td>
<span className="px-2 py-1 bg-gray-100 text-gray-400 rounded-lg font-bold text-[10px]">Chặng -</span>
</td>
<td className="px-4 py-4 text-gray-300 italic"> dụ: tham quan, Tiền ăn trưa...</td>
<td className="px-4 py-4 text-right font-black text-gray-300">0đ</td>
<td className="px-4 py-4">
<div className="flex items-center gap-1.5 text-gray-300">
<User className="w-3.5 h-3.5 opacity-20" /> Chưa dữ liệu
</div>
</td>
<td className="px-4 py-4 text-xs text-gray-200 italic">-</td>
</tr> </tr>
)) ))
)} )}