Production cleanups

This commit is contained in:
Stephanos A
2026-07-08 09:01:07 +03:00
parent 63fa6996b1
commit c7403e1a1e
21 changed files with 701 additions and 755 deletions

View File

@@ -34,7 +34,7 @@ The Passenger Backoffice Application is a comprehensive management system for th
- **Live Tracking**: Monitor trip status and real-time updates
- **Security Monitoring**: Fraud detection and audit logging
- **Comprehensive Analytics**: Revenue, occupancy, and performance reports
- **🆕 Excess Baggage Management**: Handle boarding baggage charges with agent tools
- **🆕 Excess Luggage Management**: Handle boarding baggage charges with agent tools
- **🆕 Travel Packages**: Manage pilgrimage and group travel packages with tiered pricing
- **🆕 System Health Monitoring**: Real-time API health checks and system status
- **🆕 Advanced Fare Configuration**: Dynamic pricing with segment-based rules
@@ -94,7 +94,7 @@ The application is organized into 8 main sections:
├── System Config
└── Settings
└── Enhanced Features
├── Excess Baggage
├── Excess Luggage
├── Travel Packages
├── Package Inquiries
├── Health Monitoring
@@ -2862,7 +2862,7 @@ Action: Block user
### Version 1.0.0 (January 15, 2026)
- **Complete Platform Release** - Full-featured passenger management system
- **Excess Baggage Management** - Complete boarding baggage handling with agent tools and passenger self-pay options
- **Excess Luggage Management** - Complete boarding baggage handling with agent tools and passenger self-pay options
- **Travel Packages** - Pilgrimage and group travel packages with tiered pricing, capacity management, and inquiry handling
- **System Health Monitoring** - Real-time API health checks with liveness, readiness, and performance metrics
- **System Configuration** - Centralized config management with feature flags, rate limiting, and operational controls
@@ -2920,7 +2920,7 @@ Action: Block user
## Enhanced Features
### Excess Baggage
### Excess Luggage
**Purpose**: Manage excess baggage charges at boarding with agent tools and passenger self-pay
**Access Level**: Agent, Supervisor, Admin
@@ -2941,7 +2941,7 @@ Action: Block user
└──────────────────────────────┘
```
#### Excess Baggage Process
#### Excess Luggage Process
1. **At Boarding**: Agent weighs passenger baggage
2. **If Excess**: Agent creates charge in system
@@ -2962,8 +2962,8 @@ Action: Block user
##### READ (List Charges)
1. **Access Excess Baggage Page**:
- Click **Excess Baggage** in Enhanced Features section
1. **Access Excess Luggage Page**:
- Click **Excess Luggage** in Enhanced Features section
- Shows all baggage charges
2. **Search & Filter**:
@@ -3000,7 +3000,7 @@ Action: Block user
#### Agent Workflow
1. **Weigh Baggage**: Use station scales
1. **Weigh Luggage**: Use station scales
2. **Check Allowance**: Compare to passenger's seat class allowance
3. **Create Charge**: If excess weight found
4. **Offer Payment Options**:

View File

@@ -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>

View File

@@ -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); }}

View File

@@ -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>

View File

@@ -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 (&lt;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>}

View File

@@ -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,

View File

@@ -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">

View File

@@ -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 ? (

View File

@@ -444,7 +444,7 @@ export const packageInquiriesApi = {
remove: (id: string) => apiClient.delete(`/packages/inquiries/${id}`),
};
// Excess Baggage API
// Excess Luggage API
export const excessBaggageApi = {
logCharge: (data: any) => apiClient.post<any>('/agents/excess-baggage', data),
getCharge: (id: string) => apiClient.get<any>(`/agents/excess-baggage/${id}`),