Merge pull request #603 from Tria-plc/alpha

Alpha
This commit is contained in:
Stephanos A.
2026-07-10 16:25:34 +03:00
committed by GitHub
2 changed files with 33 additions and 15 deletions

View File

@@ -37,6 +37,7 @@ function BookingsPageContent() {
const [selectedBooking, setSelectedBooking] = useState<any>(null);
const [generateTicketBooking, setGenerateTicketBooking] = useState<any>(null);
const [generateTicketForm, setGenerateTicketForm] = useState({ paymentReference: '', paymentMethod: '', notes: '' });
const [generateTicketTouched, setGenerateTicketTouched] = useState({ paymentReference: false, paymentMethod: false });
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
const [bookingToDelete, setBookingToDelete] = useState<any>(null);
const [deleteError, setDeleteError] = useState<string | null>(null);
@@ -75,6 +76,7 @@ function BookingsPageContent() {
setSelectedBooking(null);
setGenerateTicketBooking(null);
setGenerateTicketForm({ paymentReference: '', paymentMethod: '', notes: '' });
setGenerateTicketTouched({ paymentReference: false, paymentMethod: false });
},
});
@@ -276,7 +278,7 @@ function BookingsPageContent() {
const actions = [
{ label: 'View Details', onClick: (b: any) => setSelectedBooking(b), variant: 'secondary' as const, icon: Eye },
{ label: 'Generate Ticket', onClick: (b: any) => { setGenerateTicketForm({ paymentReference: '', paymentMethod: '', notes: '' }); setGenerateTicketBooking(b); }, variant: 'secondary' as const, icon: Ticket, show: (b: any) => !(b.status === 'CONFIRMED' && b.paymentIntent?.status === 'SUCCEEDED') },
{ label: 'Generate Ticket', onClick: (b: any) => { setGenerateTicketForm({ paymentReference: '', paymentMethod: '', notes: '' }); setGenerateTicketTouched({ paymentReference: false, paymentMethod: false }); setGenerateTicketBooking(b); }, variant: 'secondary' as const, icon: Ticket, show: (b: any) => !(b.status === 'CONFIRMED' && b.paymentIntent?.status === 'SUCCEEDED') },
{ label: 'Delete', onClick: (b: any) => { setDeleteError(null); setDeleteCascade(false); setDeleteCascadeChecked(false); setBookingToDelete(b); setDeleteConfirmOpen(true); }, variant: 'danger' as const, icon: Trash2 },
];
@@ -558,7 +560,7 @@ function BookingsPageContent() {
{/* Generate Ticket Modal */}
<Modal
isOpen={!!generateTicketBooking}
onClose={() => { setGenerateTicketBooking(null); setGenerateTicketForm({ paymentReference: '', paymentMethod: '', notes: '' }); forceConfirmMutation.reset(); }}
onClose={() => { setGenerateTicketBooking(null); setGenerateTicketForm({ paymentReference: '', paymentMethod: '', notes: '' }); setGenerateTicketTouched({ paymentReference: false, paymentMethod: false }); forceConfirmMutation.reset(); }}
title="Generate Ticket"
size="md"
>
@@ -573,22 +575,27 @@ function BookingsPageContent() {
</div>
<div>
<label className="label">Payment Reference / Transaction ID</label>
<label className="label">Payment Reference / Transaction ID <span className="text-red-500">*</span></label>
<input
type="text"
className="input"
className={`input${generateTicketTouched.paymentReference && !generateTicketForm.paymentReference ? ' border-red-400 dark:border-red-500' : ''}`}
placeholder="e.g. TXN-123456 or vendor receipt number"
value={generateTicketForm.paymentReference}
onChange={(e) => setGenerateTicketForm(f => ({ ...f, paymentReference: e.target.value }))}
onBlur={() => setGenerateTicketTouched(t => ({ ...t, paymentReference: true }))}
/>
{generateTicketTouched.paymentReference && !generateTicketForm.paymentReference && (
<p className="text-xs text-red-500 mt-1">Payment reference is required</p>
)}
</div>
<div>
<label className="label">Payment Method</label>
<label className="label">Payment Method <span className="text-red-500">*</span></label>
<select
className="input"
className={`input${generateTicketTouched.paymentMethod && !generateTicketForm.paymentMethod ? ' border-red-400 dark:border-red-500' : ''}`}
value={generateTicketForm.paymentMethod}
onChange={(e) => setGenerateTicketForm(f => ({ ...f, paymentMethod: e.target.value }))}
onBlur={() => setGenerateTicketTouched(t => ({ ...t, paymentMethod: true }))}
>
<option value="">Select method</option>
<option value="TELEBIRR">Telebirr</option>
@@ -599,6 +606,9 @@ function BookingsPageContent() {
<option value="CARD">Card</option>
<option value="WALLET">Wallet</option>
</select>
{generateTicketTouched.paymentMethod && !generateTicketForm.paymentMethod && (
<p className="text-xs text-red-500 mt-1">Payment method is required</p>
)}
</div>
<div>
@@ -620,19 +630,23 @@ function BookingsPageContent() {
<div className="flex justify-end gap-2 pt-2 border-t border-muted">
<ActionButton
variant="secondary"
onClick={() => { setGenerateTicketBooking(null); setGenerateTicketForm({ paymentReference: '', paymentMethod: '', notes: '' }); forceConfirmMutation.reset(); }}
onClick={() => { setGenerateTicketBooking(null); setGenerateTicketForm({ paymentReference: '', paymentMethod: '', notes: '' }); setGenerateTicketTouched({ paymentReference: false, paymentMethod: false }); forceConfirmMutation.reset(); }}
>
Cancel
</ActionButton>
<ActionButton
onClick={() => forceConfirmMutation.mutate({
bookingId: generateTicketBooking.id,
data: {
paymentReference: generateTicketForm.paymentReference || undefined,
paymentMethod: generateTicketForm.paymentMethod || undefined,
notes: generateTicketForm.notes || undefined,
},
})}
onClick={() => {
setGenerateTicketTouched({ paymentReference: true, paymentMethod: true });
if (!generateTicketForm.paymentReference || !generateTicketForm.paymentMethod) return;
forceConfirmMutation.mutate({
bookingId: generateTicketBooking.id,
data: {
paymentReference: generateTicketForm.paymentReference,
paymentMethod: generateTicketForm.paymentMethod,
notes: generateTicketForm.notes || undefined,
},
});
}}
disabled={forceConfirmMutation.isPending}
>
{forceConfirmMutation.isPending ? 'Generating…' : 'Confirm & Generate Ticket'}

View File

@@ -1,5 +1,6 @@
import Link from 'next/link';
import Image from 'next/image';
import ThemeToggle from './ThemeToggle';
// AppSidebar (which carries the logo) is `hidden lg:flex` — invisible on
// mobile, so mobile pages had no branding/way back to home at all. This is
@@ -19,6 +20,9 @@ export default function MobileTopBar() {
priority
/>
</Link>
<div className="ml-auto">
<ThemeToggle />
</div>
</div>
);
}