mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 06:28:12 +00:00
Production cleanups
This commit is contained in:
@@ -311,41 +311,25 @@ export default function ClassesPage() {
|
||||
<p className="text-xs text-muted-foreground mt-1">Per-km distance-based fare rate</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||
<div>
|
||||
<label className="label">Premium Fee (ETB)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="premiumMinor"
|
||||
className="input"
|
||||
defaultValue={editingClass?.premiumMinor ? (editingClass.premiumMinor / 100).toFixed(2) : '0.00'}
|
||||
min="0"
|
||||
step="0.01"
|
||||
placeholder="e.g., 50.00"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">Flat fee per passenger (e.g., lounge access, extra legroom)</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="label">Insurance Fee (ETB)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="insuranceFeeMinor"
|
||||
className="input"
|
||||
defaultValue={editingClass?.insuranceFeeMinor ? (editingClass.insuranceFeeMinor / 100).toFixed(2) : '0.00'}
|
||||
min="0"
|
||||
step="0.01"
|
||||
placeholder="e.g., 25.00"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">Flat fee per passenger (e.g., travel insurance)</p>
|
||||
</div>
|
||||
<input type="hidden" name="premiumMinor" value="0" />
|
||||
<div className="mt-4">
|
||||
<label className="label">Insurance Fee (ETB)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="insuranceFeeMinor"
|
||||
className="input"
|
||||
defaultValue={editingClass?.insuranceFeeMinor ? (editingClass.insuranceFeeMinor / 100).toFixed(2) : '0.00'}
|
||||
min="0"
|
||||
step="0.01"
|
||||
placeholder="e.g., 25.00"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">Flat fee per passenger (e.g., travel insurance)</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded text-sm text-blue-800 dark:text-blue-200">
|
||||
<p className="font-medium mb-1">Total Fare Calculation:</p>
|
||||
<p>Total = (Base Fare × Distance) + Premium + Insurance</p>
|
||||
<p className="mt-2 text-xs">• Premium applies per passenger</p>
|
||||
<p className="text-xs">• Insurance applies per passenger</p>
|
||||
<p>Total = (Base Fare × Distance) + Insurance</p>
|
||||
<p className="mt-2 text-xs">• Insurance applies per passenger</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -220,13 +220,6 @@ export default function CurrenciesPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="card bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 text-sm text-blue-800 dark:text-blue-300 space-y-1">
|
||||
<p className="font-semibold text-blue-900 dark:text-blue-200 mb-2">How it works</p>
|
||||
<p>• ETB is the transaction currency — all fares are stored in ETB minor units (1 ETB = 100 minor)</p>
|
||||
<p>• DJF and USD rates are used to display prices to passengers in their preferred currency</p>
|
||||
<p>• Rates apply globally; changes take effect immediately on the next booking or fare quote</p>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
isOpen={showAddModal}
|
||||
onClose={() => { setShowAddModal(false); setError(null); }}
|
||||
|
||||
@@ -82,7 +82,7 @@ export default function OperationsSection() {
|
||||
|
||||
{/* EXCESS BAGGAGE */}
|
||||
<div id="excess-baggage" className="border-t pt-8">
|
||||
<h2 className="text-2xl font-bold text-slate-900 dark:text-white mb-2">📦 Luggage (Excess Baggage)</h2>
|
||||
<h2 className="text-2xl font-bold text-slate-900 dark:text-white mb-2">📦 Luggage (Excess Luggage)</h2>
|
||||
<p className={li}>Handle excess baggage charges at boarding — passenger self-pay or agent cash collection. Access level: Agent, Supervisor, Admin.</p>
|
||||
<div className="mt-3 flex flex-wrap gap-2 text-xs">
|
||||
{['PENDING','PAID','CASH_COLLECTED','EXPIRED','WAIVED'].map(s => (
|
||||
@@ -91,7 +91,7 @@ export default function OperationsSection() {
|
||||
</div>
|
||||
</div>
|
||||
<div id="excess-baggage-how" className="border-t pt-8">
|
||||
<h3 className="text-xl font-bold text-slate-900 dark:text-white mb-4">📦 How-To: Handle Excess Baggage</h3>
|
||||
<h3 className="text-xl font-bold text-slate-900 dark:text-white mb-4">📦 How-To: Handle Excess Luggage</h3>
|
||||
<div className="space-y-3">
|
||||
<HowToStep number={1} title="Find Charges">
|
||||
<ol className={ol}><li>Click <strong>Luggage</strong> in Operations</li><li>Search by booking reference; filter by status or date</li></ol>
|
||||
|
||||
@@ -609,7 +609,7 @@ export default function PricingPage() {
|
||||
className={`px-4 py-2 font-medium border-b-2 transition-colors ${tab === 'baggage' ? 'border-primary text-primary' : 'border-transparent text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
Excess Baggage Rates
|
||||
Excess Luggage Rates
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -765,24 +765,6 @@ export default function PricingPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800">
|
||||
<h3 className="font-semibold text-blue-900 dark:text-blue-200 mb-3">Pricing Structure</h3>
|
||||
<ul className="text-sm text-blue-800 dark:text-blue-300 space-y-2">
|
||||
<li>
|
||||
• <strong>Segment Fares:</strong> Set fares for specific stop-to-stop segments (e.g., Addis → Dire Dawa)
|
||||
</li>
|
||||
<li>
|
||||
• <strong>Schedule Fares:</strong> Set custom pricing for each schedule by seat class and passenger type
|
||||
</li>
|
||||
<li>
|
||||
• <strong>Passenger Type:</strong> ADULT (5+ years) or CHILD (<5) — first child travels free, subsequent children pay full fare
|
||||
</li>
|
||||
<li>
|
||||
• <strong>Nationality-based:</strong> Override fares for specific nationalities (Ethiopian, Djiboutian, Other)
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Delete Confirmation */}
|
||||
<ConfirmDialog
|
||||
isOpen={deleteConfirm.isOpen}
|
||||
@@ -1113,7 +1095,7 @@ export default function PricingPage() {
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
{/* Baggage Allowance Modal */}
|
||||
{/* Luggage Allowance Modal */}
|
||||
<Modal isOpen={baggageModal} onClose={() => { setBaggageModal(false); setEditingAllowance(null); setBaggageError(null); }} title={editingAllowance ? 'Edit Allowance Rule' : 'Add Allowance Rule'} size="md">
|
||||
<div className="space-y-4">
|
||||
{baggageError && <div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 p-3 rounded-lg text-sm text-red-800 dark:text-red-200">{baggageError}</div>}
|
||||
|
||||
@@ -52,15 +52,28 @@ export default function SeatsPage() {
|
||||
queryFn: async () => {
|
||||
if (!selectedRoute) return null;
|
||||
const template: any[] = await routeCoachTemplatesApi.get(selectedRoute);
|
||||
if (!template?.length) return [];
|
||||
const fullCoaches = await Promise.all(
|
||||
template.map((entry: any) => fleetApi.getCoach(entry.coachId ?? entry.coach?.id))
|
||||
);
|
||||
return fullCoaches.map((coach: any, i: number) => ({
|
||||
if (template?.length) {
|
||||
const fullCoaches = await Promise.all(
|
||||
template.map((entry: any) => fleetApi.getCoach(entry.coachId ?? entry.coach?.id))
|
||||
);
|
||||
return fullCoaches.map((coach: any, i: number) => ({
|
||||
...coach,
|
||||
coachNumber: coach.number,
|
||||
positionNumber: template[i].positionNumber,
|
||||
seatArrangement: coach.arrangement,
|
||||
}));
|
||||
}
|
||||
// No template — fetch coaches from the most recent schedule for this route
|
||||
const schedules: any = await schedulesApi.getAll({ routeId: selectedRoute });
|
||||
const scheduleList: any[] = schedules?.items || schedules?.data || (Array.isArray(schedules) ? schedules : []);
|
||||
if (!scheduleList.length) return [];
|
||||
const latestSchedule = scheduleList[scheduleList.length - 1];
|
||||
const seatMap: any = await seatsApi.getSeatMap(latestSchedule.id);
|
||||
return (seatMap?.coaches || []).map((coach: any, i: number) => ({
|
||||
...coach,
|
||||
coachNumber: coach.number,
|
||||
positionNumber: template[i].positionNumber,
|
||||
seatArrangement: coach.arrangement,
|
||||
coachNumber: coach.number ?? coach.coachNumber,
|
||||
positionNumber: coach.positionNumber ?? i + 1,
|
||||
seatArrangement: coach.arrangement ?? coach.seatArrangement,
|
||||
}));
|
||||
},
|
||||
enabled: !!selectedRoute,
|
||||
|
||||
@@ -237,16 +237,6 @@ export default function TariffRatesPage() {
|
||||
</ActionButton>
|
||||
</div>
|
||||
|
||||
{/* Tariff reference card */}
|
||||
<div className="card bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800">
|
||||
<h3 className="font-semibold text-blue-900 dark:text-blue-200 mb-2">Official Tariff Formula</h3>
|
||||
<p className="text-sm text-blue-800 dark:text-blue-300 font-mono">
|
||||
Fare = KM × rate × 1.02 × ExchangeRate
|
||||
</p>
|
||||
<p className="text-xs text-blue-700 dark:text-blue-400 mt-1">
|
||||
Rate is stored as <strong>baseFareMinor = tariff_decimal × 100,000</strong> (e.g. 0.03 → 3000). The ×1.02 insurance coefficient is applied automatically by the fare engine.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<div className="relative mb-4">
|
||||
|
||||
@@ -884,11 +884,11 @@ export default function TicketsPage() {
|
||||
})()}
|
||||
</Modal>
|
||||
|
||||
{/* Excess Baggage Modal */}
|
||||
{/* Excess Luggage Modal */}
|
||||
<Modal
|
||||
isOpen={excessModalOpen}
|
||||
onClose={() => { setExcessModalOpen(false); setExcessTicket(null); setExcessResult(null); }}
|
||||
title="Log Excess Baggage"
|
||||
title="Log Excess Luggage"
|
||||
size="sm"
|
||||
>
|
||||
{excessResult ? (
|
||||
|
||||
Reference in New Issue
Block a user