mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 09:58:12 +00:00
Segment price override enabled
This commit is contained in:
@@ -0,0 +1,289 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Plus, Edit, Trash2 } from 'lucide-react';
|
||||||
|
import DataTable from '@/components/ui/DataTable';
|
||||||
|
import ActionButton from '@/components/ui/ActionButton';
|
||||||
|
import ConfirmDialog from '@/components/ui/ConfirmDialog';
|
||||||
|
import Modal from '@/components/ui/Modal';
|
||||||
|
import { useSegmentFareRules, useSegmentFareMutations, useSeatClasses, useRoutes } from './hooks';
|
||||||
|
import type { Route, SegmentFareRule, SeatClass } from './types';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { apiClient } from '@/lib/api-client';
|
||||||
|
|
||||||
|
interface RouteStop { sequence: number; station?: { name: string; code: string } }
|
||||||
|
|
||||||
|
function useRouteStops(routeId: string | null) {
|
||||||
|
const { data } = useQuery({
|
||||||
|
queryKey: ['route-stops', routeId],
|
||||||
|
queryFn: () => apiClient.get<RouteStop[]>(`/routes/${routeId}/stops`),
|
||||||
|
enabled: !!routeId,
|
||||||
|
});
|
||||||
|
return (Array.isArray(data) ? data : (data as any)?.items ?? []) as RouteStop[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function useExchangeRates() {
|
||||||
|
const { data } = useQuery({
|
||||||
|
queryKey: ['exchange-rates'],
|
||||||
|
queryFn: () => apiClient.get<any[]>('/currencies'),
|
||||||
|
});
|
||||||
|
const rates: any[] = Array.isArray(data) ? data : (data as any)?.items ?? [];
|
||||||
|
// Build ETB→X lookup: rate value
|
||||||
|
const rateMap: Record<string, number> = {};
|
||||||
|
for (const r of rates) {
|
||||||
|
if (r.fromCurrency === 'ETB') rateMap[r.toCurrency] = r.rate;
|
||||||
|
}
|
||||||
|
return rateMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatFixed(amountMinor: number, currency = 'ETB', rateMap: Record<string, number> = {}) {
|
||||||
|
const etb = (amountMinor / 100).toFixed(2);
|
||||||
|
if (currency === 'ETB') return `ETB ${etb}`;
|
||||||
|
const rate = rateMap[currency];
|
||||||
|
if (!rate) return `ETB ${etb}`;
|
||||||
|
const converted = ((amountMinor / 100) * rate).toFixed(2);
|
||||||
|
return `ETB ${etb} ≈ ${currency} ${converted}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props { routes: Route[] }
|
||||||
|
|
||||||
|
type FormState = { isOpen: boolean; rule: SegmentFareRule | null; error: string | null };
|
||||||
|
|
||||||
|
export default function SegmentOverridesTab({ routes }: Props) {
|
||||||
|
const [selectedRouteId, setSelectedRouteId] = useState<string | null>(null);
|
||||||
|
const [form, setForm] = useState<FormState>({ isOpen: false, rule: null, error: null });
|
||||||
|
const [deleteConfirm, setDeleteConfirm] = useState<{ isOpen: boolean; id: string | null; name: string; error?: string }>({ isOpen: false, id: null, name: '' });
|
||||||
|
|
||||||
|
const { segmentFares, isLoading } = useSegmentFareRules(selectedRouteId);
|
||||||
|
const { create, update, remove } = useSegmentFareMutations(selectedRouteId);
|
||||||
|
const { allClasses } = useSeatClasses();
|
||||||
|
const stops = useRouteStops(selectedRouteId);
|
||||||
|
const rateMap = useExchangeRates();
|
||||||
|
|
||||||
|
const stopLabel = (seq: number) => {
|
||||||
|
const s = stops.find(st => st.sequence === seq);
|
||||||
|
return s?.station ? `${s.station.name} (${s.station.code})` : `Stop ${seq}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setForm(prev => ({ ...prev, error: null }));
|
||||||
|
const fd = new FormData(e.currentTarget);
|
||||||
|
const baseFareMinor = Math.round(Number(fd.get('baseFareMinor')) * 100);
|
||||||
|
try {
|
||||||
|
if (form.rule) {
|
||||||
|
await update.mutateAsync({ id: form.rule.id, baseFareMinor });
|
||||||
|
} else {
|
||||||
|
await create.mutateAsync({
|
||||||
|
routeId: selectedRouteId,
|
||||||
|
originStopSequence: Number(fd.get('originStopSequence')),
|
||||||
|
destinationStopSequence: Number(fd.get('destinationStopSequence')),
|
||||||
|
seatClassId: fd.get('seatClassId') as string,
|
||||||
|
nationality: (fd.get('nationality') as string) || null,
|
||||||
|
baseFareMinor,
|
||||||
|
validFrom: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setForm({ isOpen: false, rule: null, error: null });
|
||||||
|
} catch (err: any) {
|
||||||
|
setForm(prev => ({ ...prev, error: err?.response?.data?.message ?? err?.message ?? 'Failed to save' }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
try {
|
||||||
|
await remove.mutateAsync(deleteConfirm.id!);
|
||||||
|
setDeleteConfirm({ isOpen: false, id: null, name: '' });
|
||||||
|
} catch (err: any) {
|
||||||
|
setDeleteConfirm(prev => ({ ...prev, error: err?.response?.data?.message ?? err?.message ?? 'Delete failed' }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
key: 'segment', label: 'Segment',
|
||||||
|
render: (r: SegmentFareRule) => (
|
||||||
|
<span className="font-medium">
|
||||||
|
{stopLabel(r.originStopSequence)} → {stopLabel(r.destinationStopSequence)}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'seatClass', label: 'Seat Class',
|
||||||
|
render: (r: SegmentFareRule) => <span>{r.seatClass?.name ?? r.seatClassId}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'nationality', label: 'Nationality',
|
||||||
|
render: (r: SegmentFareRule) => <span className="text-sm">{r.nationality ?? 'All'}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'baseFareMinor', label: 'Fixed Price',
|
||||||
|
render: (r: SegmentFareRule) => (
|
||||||
|
<span className="font-mono font-medium text-sm">{formatFixed(r.baseFareMinor, 'ETB', rateMap)}</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'converted', label: 'Converted',
|
||||||
|
render: (r: SegmentFareRule) => (
|
||||||
|
<div className="text-xs text-muted-foreground space-y-0.5">
|
||||||
|
{Object.entries(rateMap).map(([cur, rate]) => (
|
||||||
|
<div key={cur}>{cur} {((r.baseFareMinor / 100) * rate).toFixed(2)}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'validFrom', label: 'Valid From',
|
||||||
|
render: (r: SegmentFareRule) => <span className="text-sm">{new Date(r.validFrom).toLocaleDateString()}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'validUntil', label: 'Valid Until',
|
||||||
|
render: (r: SegmentFareRule) => <span className="text-sm">{r.validUntil ? new Date(r.validUntil).toLocaleDateString() : '—'}</span>,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const isAddMode = form.isOpen && !form.rule;
|
||||||
|
const isPending = create.isPending || update.isPending;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mb-3 text-sm text-muted-foreground bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg px-4 py-2">
|
||||||
|
Segment overrides set a <strong>fixed total price</strong> for a specific origin→destination stop pair, bypassing per-km calculation.
|
||||||
|
Precedence: <strong>Segment Override</strong> → Route Override → Seat Class Tariff.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<select
|
||||||
|
className="input w-64"
|
||||||
|
value={selectedRouteId ?? ''}
|
||||||
|
onChange={e => setSelectedRouteId(e.target.value || null)}
|
||||||
|
>
|
||||||
|
<option value="">Select route</option>
|
||||||
|
{routes.map(r => (
|
||||||
|
<option key={r.id} value={r.id}>{r.name}{r.code ? ` (${r.code})` : ''}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<ActionButton icon={Plus} onClick={() => setForm({ isOpen: true, rule: null, error: null })} disabled={!selectedRouteId}>
|
||||||
|
Add Segment Override
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!selectedRouteId ? (
|
||||||
|
<div className="text-center py-8 text-muted-foreground">Select a route above to view its segment overrides.</div>
|
||||||
|
) : (
|
||||||
|
<DataTable
|
||||||
|
data={segmentFares}
|
||||||
|
columns={columns}
|
||||||
|
actions={[
|
||||||
|
{ label: 'Edit', icon: Edit, variant: 'secondary' as const, onClick: (r: SegmentFareRule) => setForm({ isOpen: true, rule: r, error: null }) },
|
||||||
|
{ label: 'Delete', icon: Trash2, variant: 'danger' as const, onClick: (r: SegmentFareRule) => setDeleteConfirm({ isOpen: true, id: r.id, name: `${stopLabel(r.originStopSequence)} → ${stopLabel(r.destinationStopSequence)}` }) },
|
||||||
|
]}
|
||||||
|
loading={isLoading}
|
||||||
|
emptyMessage="No segment overrides for this route."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
isOpen={form.isOpen}
|
||||||
|
onClose={() => setForm({ isOpen: false, rule: null, error: null })}
|
||||||
|
title={isAddMode ? 'Add Segment Override' : 'Edit Segment Override'}
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<form key={form.rule?.id ?? 'add'} onSubmit={handleFormSubmit} className="space-y-4">
|
||||||
|
{form.error && (
|
||||||
|
<div className="rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 p-3 text-sm text-red-800 dark:text-red-200">{form.error}</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isAddMode ? (
|
||||||
|
<div className="grid grid-cols-1 gap-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="label">Origin Stop *</label>
|
||||||
|
<select name="originStopSequence" className="input w-full" required>
|
||||||
|
<option value="">Select origin</option>
|
||||||
|
{stops.map(s => (
|
||||||
|
<option key={s.sequence} value={s.sequence}>{stopLabel(s.sequence)}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="label">Destination Stop *</label>
|
||||||
|
<select name="destinationStopSequence" className="input w-full" required>
|
||||||
|
<option value="">Select destination</option>
|
||||||
|
{stops.map(s => (
|
||||||
|
<option key={s.sequence} value={s.sequence}>{stopLabel(s.sequence)}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="label">Seat Class *</label>
|
||||||
|
<select name="seatClassId" className="input w-full" required>
|
||||||
|
<option value="">Select seat class</option>
|
||||||
|
{allClasses.filter((c: SeatClass) => c.isActive).map((c: SeatClass) => (
|
||||||
|
<option key={c.id} value={c.id}>{c.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="label">Nationality <span className="text-muted-foreground font-normal">(optional — leave blank for all)</span></label>
|
||||||
|
<select name="nationality" className="input w-full">
|
||||||
|
<option value="">All nationalities</option>
|
||||||
|
<option value="Ethiopian">Ethiopian</option>
|
||||||
|
<option value="Djiboutian">Djiboutian</option>
|
||||||
|
<option value="Other">Other (International)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="p-3 rounded-lg bg-muted/50 text-sm text-muted-foreground">
|
||||||
|
<span className="font-medium text-foreground">
|
||||||
|
{form.rule && stopLabel(form.rule.originStopSequence)} → {form.rule && stopLabel(form.rule.destinationStopSequence)}
|
||||||
|
</span>
|
||||||
|
{' · '}{form.rule?.seatClass?.name ?? form.rule?.seatClassId}
|
||||||
|
{form.rule?.nationality ? ` · ${form.rule.nationality}` : ' · All nationalities'}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="label">Fixed Price (ETB) *</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="baseFareMinor"
|
||||||
|
className="input"
|
||||||
|
defaultValue={form.rule ? (form.rule.baseFareMinor / 100).toFixed(2) : ''}
|
||||||
|
min="0" step="0.01" required
|
||||||
|
placeholder="e.g. 350.00"
|
||||||
|
/>
|
||||||
|
{Object.keys(rateMap).length > 0 && (
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
Exchange rates applied at booking: {Object.entries(rateMap).map(([c, r]) => `1 ETB = ${r} ${c}`).join(', ')}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-2 pt-2">
|
||||||
|
<ActionButton type="button" variant="secondary" onClick={() => setForm({ isOpen: false, rule: null, error: null })}>Cancel</ActionButton>
|
||||||
|
<ActionButton type="submit" loading={isPending}>
|
||||||
|
{isAddMode ? 'Create Override' : 'Update Override'}
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={deleteConfirm.isOpen}
|
||||||
|
onClose={() => setDeleteConfirm({ isOpen: false, id: null, name: '' })}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
title="Delete Segment Override"
|
||||||
|
message={`Delete the segment override for "${deleteConfirm.name}"? The route override or global tariff will apply instead.`}
|
||||||
|
confirmText="Delete"
|
||||||
|
isDanger
|
||||||
|
isLoading={remove.isPending}
|
||||||
|
error={deleteConfirm.error}
|
||||||
|
warning="Removing this override means bookings on this segment will fall back to the route override or global tariff rate."
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { apiClient } from '@/lib/api-client';
|
import { apiClient } from '@/lib/api-client';
|
||||||
import type { SeatClass, CoachType, Route, RouteFareRule, BaggageAllowance } from './types';
|
import type { SeatClass, CoachType, Route, RouteFareRule, SegmentFareRule, BaggageAllowance } from './types';
|
||||||
|
|
||||||
function toArray<T>(data: unknown): T[] {
|
function toArray<T>(data: unknown): T[] {
|
||||||
if (Array.isArray(data)) return data as T[];
|
if (Array.isArray(data)) return data as T[];
|
||||||
@@ -32,6 +32,35 @@ export function useRoutes() {
|
|||||||
return { routes: toArray<Route>(data) };
|
return { routes: toArray<Route>(data) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useSegmentFareRules(routeId: string | null) {
|
||||||
|
const { data, isLoading, refetch } = useQuery({
|
||||||
|
queryKey: ['segment-fare-rules', routeId],
|
||||||
|
queryFn: () => apiClient.get<unknown>(`/schedules/routes/${routeId}/segment-fares`),
|
||||||
|
enabled: !!routeId,
|
||||||
|
});
|
||||||
|
return { segmentFares: toArray<SegmentFareRule>(data), isLoading, refetch };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSegmentFareMutations(routeId: string | null) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const invalidate = () => queryClient.invalidateQueries({ queryKey: ['segment-fare-rules', routeId] });
|
||||||
|
|
||||||
|
const create = useMutation({
|
||||||
|
mutationFn: (data: any) => apiClient.post('/schedules/segment-fares', data),
|
||||||
|
onSuccess: invalidate,
|
||||||
|
});
|
||||||
|
const update = useMutation({
|
||||||
|
mutationFn: ({ id, ...data }: any) => apiClient.patch(`/schedules/segment-fares/${id}`, data),
|
||||||
|
onSuccess: invalidate,
|
||||||
|
});
|
||||||
|
const remove = useMutation({
|
||||||
|
mutationFn: (id: string) => apiClient.delete(`/schedules/segment-fares/${id}`),
|
||||||
|
onSuccess: invalidate,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { create, update, remove };
|
||||||
|
}
|
||||||
|
|
||||||
export function useRouteFareRules(routeId: string | null) {
|
export function useRouteFareRules(routeId: string | null) {
|
||||||
const { data, isLoading, refetch } = useQuery({
|
const { data, isLoading, refetch } = useQuery({
|
||||||
queryKey: ['route-fare-rules', routeId],
|
queryKey: ['route-fare-rules', routeId],
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Plus } from 'lucide-react';
|
|||||||
import ActionButton from '@/components/ui/ActionButton';
|
import ActionButton from '@/components/ui/ActionButton';
|
||||||
import TariffTab from './TariffTab';
|
import TariffTab from './TariffTab';
|
||||||
import OverridesTab from './OverridesTab';
|
import OverridesTab from './OverridesTab';
|
||||||
|
import SegmentOverridesTab from './SegmentOverridesTab';
|
||||||
import BaggageTab from './BaggageTab';
|
import BaggageTab from './BaggageTab';
|
||||||
import RateModal from './RateModal';
|
import RateModal from './RateModal';
|
||||||
import { useSeatClasses, useCoachTypes, useRoutes, useSeatClassMutations, useRouteFareRuleMutations } from './hooks';
|
import { useSeatClasses, useCoachTypes, useRoutes, useSeatClassMutations, useRouteFareRuleMutations } from './hooks';
|
||||||
@@ -56,6 +57,7 @@ export default function TariffRatesPage() {
|
|||||||
const tabs: { key: TabType; label: string }[] = [
|
const tabs: { key: TabType; label: string }[] = [
|
||||||
{ key: 'tariff', label: 'Seat Class Tariffs' },
|
{ key: 'tariff', label: 'Seat Class Tariffs' },
|
||||||
{ key: 'overrides', label: 'Route Overrides' },
|
{ key: 'overrides', label: 'Route Overrides' },
|
||||||
|
{ key: 'segment-overrides', label: 'Segment Overrides' },
|
||||||
{ key: 'baggage', label: 'Excess Luggage Rates' },
|
{ key: 'baggage', label: 'Excess Luggage Rates' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -68,7 +70,7 @@ export default function TariffRatesPage() {
|
|||||||
Manage per-km fare rates and excess luggage allowances per the official EDR tariff policy
|
Manage per-km fare rates and excess luggage allowances per the official EDR tariff policy
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{tab !== 'overrides' && (
|
{tab !== 'overrides' && tab !== 'segment-overrides' && (
|
||||||
<ActionButton icon={Plus} onClick={() => {
|
<ActionButton icon={Plus} onClick={() => {
|
||||||
if (tab === 'baggage') {
|
if (tab === 'baggage') {
|
||||||
setShowBaggageModal(true);
|
setShowBaggageModal(true);
|
||||||
@@ -113,6 +115,10 @@ export default function TariffRatesPage() {
|
|||||||
<OverridesTab routes={routes} />
|
<OverridesTab routes={routes} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{tab === 'segment-overrides' && (
|
||||||
|
<SegmentOverridesTab routes={routes} />
|
||||||
|
)}
|
||||||
|
|
||||||
{tab === 'baggage' && (
|
{tab === 'baggage' && (
|
||||||
<BaggageTab
|
<BaggageTab
|
||||||
allClasses={allClasses}
|
allClasses={allClasses}
|
||||||
|
|||||||
@@ -21,6 +21,22 @@ export interface Route {
|
|||||||
code?: string;
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SegmentFareRule {
|
||||||
|
id: string;
|
||||||
|
routeId: string;
|
||||||
|
originStopSequence: number;
|
||||||
|
destinationStopSequence: number;
|
||||||
|
seatClassId: string;
|
||||||
|
baseFareMinor: number;
|
||||||
|
nationality?: string | null;
|
||||||
|
currency: string;
|
||||||
|
validFrom: string;
|
||||||
|
validUntil?: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
seatClass?: SeatClass;
|
||||||
|
route?: Route;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RouteFareRule {
|
export interface RouteFareRule {
|
||||||
id: string;
|
id: string;
|
||||||
routeId: string;
|
routeId: string;
|
||||||
@@ -44,4 +60,4 @@ export interface BaggageAllowance {
|
|||||||
seatClass?: SeatClass;
|
seatClass?: SeatClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TabType = 'tariff' | 'overrides' | 'baggage';
|
export type TabType = 'tariff' | 'overrides' | 'segment-overrides' | 'baggage';
|
||||||
|
|||||||
@@ -966,7 +966,7 @@ export default function SeatsPage() {
|
|||||||
|
|
||||||
commitSeatAssignment(seatId);
|
commitSeatAssignment(seatId);
|
||||||
},
|
},
|
||||||
[passengerSeatMap, activePassengerIndex, validSeats, getSeatFare, commitSeatAssignment, isPackageBooking, packageTierPriceMinor, packageId, priceTierId, packageName, packageDepartureStationId, packageDepartureStationName, setPackageContext, isRoundTrip],
|
[passengerSeatMap, activePassengerIndex, validSeats, getSeatFare, commitSeatAssignment, isPackageBooking, packageTierPriceMinor, packageId, priceTierId, packageName, packageDepartureStationId, packageDepartureStationName, setPackageContext, isRoundTrip, currentSchedule?.displayCurrency],
|
||||||
);
|
);
|
||||||
|
|
||||||
const allSeatsAssigned =
|
const allSeatsAssigned =
|
||||||
@@ -1927,6 +1927,7 @@ export default function SeatsPage() {
|
|||||||
? allCoachSeats?.find((s: any) => s.id === assignedSeatId)
|
? allCoachSeats?.find((s: any) => s.id === assignedSeatId)
|
||||||
: null;
|
: null;
|
||||||
const seatLabel = assignedSeat ? buildSeatLabel(assignedSeat) : "";
|
const seatLabel = assignedSeat ? buildSeatLabel(assignedSeat) : "";
|
||||||
|
const seatFare = assignedSeat ? getSeatFare(assignedSeat) : null;
|
||||||
const isActive = i === activePassengerIndex;
|
const isActive = i === activePassengerIndex;
|
||||||
const isClickable = i <= maxSelectableIndex;
|
const isClickable = i <= maxSelectableIndex;
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user