Sửa lỗi hiển thị bảng liệt kê chi phí
This commit is contained in:
Vendored
+12
@@ -176,6 +176,12 @@ let PublicTourController = class PublicTourController {
|
||||
legs: {
|
||||
orderBy: { sequence: 'asc' },
|
||||
include: {
|
||||
expenses: {
|
||||
include: {
|
||||
location: { select: { name: true, plannedStart: true } },
|
||||
paidBy: { select: { name: true } }
|
||||
}
|
||||
},
|
||||
locations: {
|
||||
orderBy: { plannedStart: 'asc' },
|
||||
include: { _count: { select: { comments: true } } }
|
||||
@@ -432,6 +438,12 @@ let TourController = class TourController {
|
||||
legs: {
|
||||
orderBy: { sequence: 'asc' },
|
||||
include: {
|
||||
expenses: {
|
||||
include: {
|
||||
location: { select: { name: true, plannedStart: true } },
|
||||
paidBy: { select: { name: true } }
|
||||
}
|
||||
},
|
||||
locations: {
|
||||
orderBy: { plannedStart: 'asc' },
|
||||
include: { _count: { select: { comments: true } } }
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -123,6 +123,12 @@ class PublicTourController {
|
||||
legs: {
|
||||
orderBy: { sequence: 'asc' },
|
||||
include: {
|
||||
expenses: {
|
||||
include: {
|
||||
location: { select: { name: true, plannedStart: true } },
|
||||
paidBy: { select: { name: true } }
|
||||
}
|
||||
},
|
||||
locations: {
|
||||
orderBy: { plannedStart: 'asc' },
|
||||
include: { _count: { select: { comments: true } } }
|
||||
@@ -427,6 +433,12 @@ class TourController {
|
||||
legs: {
|
||||
orderBy: { sequence: 'asc' },
|
||||
include: {
|
||||
expenses: {
|
||||
include: {
|
||||
location: { select: { name: true, plannedStart: true } },
|
||||
paidBy: { select: { name: true } }
|
||||
}
|
||||
},
|
||||
locations: {
|
||||
orderBy: { plannedStart: 'asc' },
|
||||
include: { _count: { select: { comments: true } } }
|
||||
|
||||
@@ -96,7 +96,7 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
|
||||
type: editingLocation.type || 'VISIT',
|
||||
legId: editingLocation.legId || '',
|
||||
note: editingLocation.note || '',
|
||||
expenseAmount: expense?.amount?.toString() || '',
|
||||
expenseAmount: expense?.amount ? Number(expense.amount).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".") : '',
|
||||
expenseCategory: expense?.category || 'OTHER',
|
||||
expenseDescription: expense?.description || '',
|
||||
expenseNote: expense?.note || '',
|
||||
@@ -155,6 +155,7 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
|
||||
try {
|
||||
const payload: any = {
|
||||
...formData,
|
||||
expenseAmount: formData.expenseAmount.replace(/\./g, ''), // Loại bỏ dấu chấm trước khi gửi
|
||||
legId: currentLegId,
|
||||
latitude: parseFloat(formData.latitude 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>
|
||||
<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"
|
||||
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>
|
||||
<label className="block text-xs font-bold text-gray-600 mb-1">Loại dịch vụ</label>
|
||||
|
||||
@@ -56,16 +56,24 @@ export const ExpenseManager = () => {
|
||||
const avgPerLeg = legs.length > 0 ? totalAmount / legs.length : 0;
|
||||
|
||||
// Danh sách phẳng tất cả chi phí để hiển thị bảng
|
||||
const flatExpenses = (legs || []).flatMap(leg =>
|
||||
(leg.expenses || []).map((exp: any) => ({
|
||||
const flatExpenses: any[] = [];
|
||||
[...(legs || [])].sort((a, b) => a.sequence - b.sequence).forEach((leg, legIdx) => {
|
||||
const legExpenses = (leg.expenses || []).map((exp: any) => ({
|
||||
...exp,
|
||||
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
|
||||
}))
|
||||
).sort((a, b) => {
|
||||
if (!a.date || !b.date) return 0;
|
||||
return new Date(a.date).getTime() - new Date(b.date).getTime();
|
||||
})).sort((a: any, b: any) => {
|
||||
if (!a.date || !b.date) return 0;
|
||||
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);
|
||||
@@ -126,10 +134,10 @@ export const ExpenseManager = () => {
|
||||
|
||||
// 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 tableRows = stats.list.map((exp: any, index: number) => [
|
||||
index + 1,
|
||||
exp.date ? format(new Date(exp.date), 'dd/MM HH:mm') : '--/--',
|
||||
`Chặng ${exp.legSequence}`,
|
||||
const tableRows = stats.list.map((exp: any) => [
|
||||
exp.isFirstInLeg ? exp.legSequence : '',
|
||||
exp.date ? format(new Date(exp.date), 'd/M - p') : '--/--',
|
||||
exp.isFirstInLeg ? `Chặng ${exp.legSequence}` : '',
|
||||
exp.description || 'Không có mô tả',
|
||||
Number(exp.amount).toLocaleString(),
|
||||
exp.paidBy?.name || 'Chưa rõ'
|
||||
@@ -305,22 +313,35 @@ export const ExpenseManager = () => {
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-50">
|
||||
{stats.list.length > 0 ? (
|
||||
stats.list.map((exp: any, index: number) => (
|
||||
<tr key={exp.id} className="hover:bg-blue-50/20 transition-colors text-sm">
|
||||
<td className="px-4 py-4 text-center font-bold text-gray-400">{index + 1}</td>
|
||||
stats.list.map((exp: any) => (
|
||||
<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-black text-gray-400">
|
||||
{exp.isFirstInLeg ? exp.legDisplayIndex : ''}
|
||||
</td>
|
||||
<td className="px-4 py-4">
|
||||
<div className="flex items-center gap-1.5 text-gray-600 font-medium">
|
||||
<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>
|
||||
</td>
|
||||
<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]">
|
||||
Chặng {exp.legSequence}
|
||||
</span>
|
||||
{exp.isFirstInLeg ? (
|
||||
<span className="px-2 py-1 bg-blue-50 text-blue-600 rounded-lg font-bold text-[10px]">
|
||||
Chặng {exp.legSequence}
|
||||
</span>
|
||||
) : (
|
||||
<div className="ml-6 border-l-2 border-blue-100/50 h-4" />
|
||||
)}
|
||||
</td>
|
||||
<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 className="px-4 py-4 text-right font-black text-blue-600">
|
||||
{Number(exp.amount).toLocaleString()}đ
|
||||
@@ -337,26 +358,15 @@ export const ExpenseManager = () => {
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
// Khung bảng mẫu khi chưa có dữ liệu (Placeholder)
|
||||
[1, 2, 3].map((i) => (
|
||||
<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">
|
||||
<div className="flex items-center gap-1.5 text-gray-300 font-medium">
|
||||
<Calendar className="w-3.5 h-3.5 opacity-20" /> --/-- --:--
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-4 whitespace-nowrap">
|
||||
<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">Ví dụ: Vé 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 có dữ liệu
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-4 text-xs text-gray-200 italic">-</td>
|
||||
<td className="px-4 py-4 text-gray-300">--/-- --:--</td>
|
||||
<td className="px-4 py-4 text-gray-300">Chặng -</td>
|
||||
<td className="px-4 py-4 text-gray-300 italic">Chưa có dữ liệu...</td>
|
||||
<td className="px-4 py-4 text-right text-gray-300">0đ</td>
|
||||
<td className="px-4 py-4 text-gray-300">Chưa rõ</td>
|
||||
<td className="px-4 py-4 text-gray-200">-</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user