This commit is contained in:
Iliyan Angelov
2025-11-20 21:06:30 +02:00
parent 44e11520c5
commit a38ab4fa82
77 changed files with 7169 additions and 360 deletions

View File

@@ -1,20 +1,23 @@
import React, { useEffect, useState } from 'react';
import { Search, Eye, XCircle, CheckCircle, Loader2 } from 'lucide-react';
import { bookingService, Booking } from '../../services/api';
import { Search, Eye, XCircle, CheckCircle, Loader2, FileText } from 'lucide-react';
import { bookingService, Booking, invoiceService } from '../../services/api';
import { toast } from 'react-toastify';
import Loading from '../../components/common/Loading';
import Pagination from '../../components/common/Pagination';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
import { parseDateLocal } from '../../utils/format';
import { useNavigate } from 'react-router-dom';
const BookingManagementPage: React.FC = () => {
const { formatCurrency } = useFormatCurrency();
const navigate = useNavigate();
const [bookings, setBookings] = useState<Booking[]>([]);
const [loading, setLoading] = useState(true);
const [selectedBooking, setSelectedBooking] = useState<Booking | null>(null);
const [showDetailModal, setShowDetailModal] = useState(false);
const [updatingBookingId, setUpdatingBookingId] = useState<number | null>(null);
const [cancellingBookingId, setCancellingBookingId] = useState<number | null>(null);
const [creatingInvoice, setCreatingInvoice] = useState(false);
const [filters, setFilters] = useState({
search: '',
status: '',
@@ -80,6 +83,32 @@ const BookingManagementPage: React.FC = () => {
}
};
const handleCreateInvoice = async (bookingId: number) => {
try {
setCreatingInvoice(true);
// Ensure bookingId is a number
const invoiceData = {
booking_id: Number(bookingId),
};
const response = await invoiceService.createInvoice(invoiceData);
if (response.status === 'success' && response.data?.invoice) {
toast.success('Invoice created successfully!');
setShowDetailModal(false);
navigate(`/admin/invoices/${response.data.invoice.id}`);
} else {
throw new Error('Failed to create invoice');
}
} catch (error: any) {
const errorMessage = error.response?.data?.detail || error.response?.data?.message || error.message || 'Unable to create invoice';
toast.error(errorMessage);
console.error('Invoice creation error:', error);
} finally {
setCreatingInvoice(false);
}
};
const getStatusBadge = (status: string) => {
const badges: Record<string, { bg: string; text: string; label: string; border: string }> = {
pending: {
@@ -622,7 +651,24 @@ const BookingManagementPage: React.FC = () => {
</div>
{/* Modal Footer */}
<div className="mt-8 pt-6 border-t border-slate-200 flex justify-end">
<div className="mt-8 pt-6 border-t border-slate-200 flex justify-between items-center">
<button
onClick={() => handleCreateInvoice(selectedBooking.id)}
disabled={creatingInvoice}
className="flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-amber-500 to-amber-600 text-white rounded-xl font-semibold hover:from-amber-600 hover:to-amber-700 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 shadow-lg hover:shadow-xl"
>
{creatingInvoice ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
Creating Invoice...
</>
) : (
<>
<FileText className="w-5 h-5" />
Create Invoice
</>
)}
</button>
<button
onClick={() => setShowDetailModal(false)}
className="px-8 py-3 bg-gradient-to-r from-slate-700 to-slate-800 text-white rounded-xl font-semibold hover:from-slate-800 hover:to-slate-900 transition-all duration-200 shadow-lg hover:shadow-xl border border-slate-600"