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: {
|
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 } } }
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -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 } } }
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -56,18 +56,26 @@ 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);
|
||||||
const weightedCount = adults + (children * childRateFactor);
|
const weightedCount = adults + (children * childRateFactor);
|
||||||
const adultPrice = totalAmount / weightedCount;
|
const adultPrice = totalAmount / weightedCount;
|
||||||
@@ -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">
|
||||||
|
{exp.isFirstInLeg ? (
|
||||||
<span className="px-2 py-1 bg-blue-50 text-blue-600 rounded-lg font-bold text-[10px]">
|
<span className="px-2 py-1 bg-blue-50 text-blue-600 rounded-lg font-bold text-[10px]">
|
||||||
Chặng {exp.legSequence}
|
Chặng {exp.legSequence}
|
||||||
</span>
|
</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 có 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 rõ</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">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>
|
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user