Merge remote-tracking branch 'origin/dev' into tests

Merging remote repo
This commit is contained in:
Muluhabt
2026-07-27 14:44:50 +03:00
236 changed files with 17780 additions and 6194 deletions

View File

@@ -234,7 +234,7 @@ function DashboardPageContent() {
)}
{/* Stat cards */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
<StatCard
icon={
<BookOpen className="h-4 w-4 text-blue-600 dark:text-blue-400" />
@@ -257,7 +257,28 @@ function DashboardPageContent() {
},
]}
/>
{/* Tickets card hidden temporarily */}
<StatCard
icon={
<Ticket className="h-4 w-4 text-emerald-600 dark:text-emerald-400" />
}
iconBg="bg-emerald-100 dark:bg-emerald-900/30"
label="Tickets"
total={stats?.totalTickets ?? 0}
loading={statsLoading}
href="/tickets"
rows={[
{
label: "Regular",
value: stats?.totalNormalTickets ?? 0,
href: "/tickets",
},
{
label: "Package",
value: stats?.totalPackageTickets ?? 0,
href: "/tickets",
},
]}
/>
{/* Revenue card */}
<div className="card flex flex-col gap-3">

View File

@@ -10,7 +10,7 @@ import ActionButton from '@/components/ui/ActionButton';
import ConfirmDialog from '@/components/ui/ConfirmDialog';
import Modal from '@/components/ui/Modal';
import Image from 'next/image';
import { ticketsApi, apiClient, stationsApi, excessBaggageApi } from '@/lib/api';
import { ticketsApi, apiClient, stationsApi, excessBaggageApi, bookingsApi } from '@/lib/api';
import Pagination from '@/components/ui/Pagination';
import { formatDateTime, formatCurrency, formatDateTimeShort } from '@/lib/utils';
import { useAuthStore } from '@/lib/auth-store';
@@ -100,10 +100,36 @@ export default function TicketsPage() {
const [generateMissingResult, setGenerateMissingResult] = useState<any>(null);
const { data: hasMissingTickets } = useQuery({
queryKey: ['bookings-missing-tickets'],
queryFn: async () => {
const res = await bookingsApi.getAll({ status: 'CONFIRMED', paymentStatus: 'SUCCEEDED', take: 50 });
const items: any[] = (res as any)?.items ?? (Array.isArray(res) ? res : []);
return items.some((b: any) => !b.tickets?.length);
},
refetchInterval: 30_000,
});
const generateMissingMutation = useMutation({
mutationFn: () => ticketsApi.generateMissing(),
mutationFn: async () => {
let totalGenerated = 0;
let totalFailed = 0;
let totalProcessed = 0;
let remaining = 1;
while (remaining > 0) {
const result: any = await ticketsApi.generateMissing(10);
totalGenerated += result.generated ?? 0;
totalFailed += result.failed ?? 0;
totalProcessed += result.processed ?? 0;
remaining = result.remaining ?? 0;
}
return { generated: totalGenerated, processed: totalProcessed, failed: totalFailed };
},
onSuccess: (result: any) => {
queryClient.invalidateQueries({ queryKey: ['tickets'] });
queryClient.invalidateQueries({ queryKey: ['bookings-missing-tickets'] });
setGenerateMissingResult(result);
setSuccessMessage(`Generated ${result.generated} ticket(s) for ${result.processed} booking(s)${result.failed ? ` (${result.failed} failed)` : ''}`);
setTimeout(() => setSuccessMessage(''), 6000);
@@ -562,14 +588,16 @@ export default function TicketsPage() {
<p className="text-muted-foreground">Manage tickets and validations</p>
</div>
<div className="flex items-center gap-2">
<ActionButton
icon={Download}
variant="secondary"
loading={generateMissingMutation.isPending}
onClick={() => generateMissingMutation.mutate()}
>
Generate Missing
</ActionButton>
{hasMissingTickets && (
<ActionButton
icon={Download}
variant="secondary"
loading={generateMissingMutation.isPending}
onClick={() => generateMissingMutation.mutate()}
>
Generate Missing
</ActionButton>
)}
<ActionButton icon={Download} variant="export" onClick={() => setExportModalOpen(true)}>Export</ActionButton>
</div>
</div>