Redesign payment verification UI with Mantine components and fix route direction logic

- Refactor CheckPaymentPage and PaymentFailurePage to use Mantine UI components with modern card-based layouts
- Add gradient headers, themed icons, and improved visual hierarchy for payment status states
- Implement PaymentCard wrapper component for consistent styling across payment pages
- Update route direction logic to trim country values and simplify Djibouti import/export detection
- Replace shadcn/ui components with M
This commit is contained in:
marshal
2026-06-19 00:42:27 +03:00
parent 264df3b854
commit 8264df133f
5 changed files with 800 additions and 150 deletions

View File

@@ -0,0 +1,156 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* Full wagon re-seed — runs in this order:
*
* 1. DELETE all existing wagons (hard delete, not soft).
* 2. UPSERT all 10 standard wagon types so they are guaranteed to exist.
* 3. INSERT 50 wagons per wagon type (500 total), distributed evenly across
* the 5 main operational yards (10 wagons per yard per type):
*
* KALITY — Kality Rail Terminal
* MOJO — Mojo Dry Port
* DIRE_DAWA — Dire Dawa Yard
* DJIB_PORT — Djibouti Port Terminal
* NAGAD — Nagad Terminal, Djibouti
*
* Wagon numbers follow the pattern <TYPE_CODE>-NNNN (e.g. NW5-0001 … NW5-0050).
* Yard IDs are fetched live from freight.yards so the migration is safe across
* all environments regardless of UUID values.
*/
export class SeedWagonsWithYardAssignment1784000000001
implements MigrationInterface
{
name = 'SeedWagonsWithYardAssignment1784000000001';
public async up(queryRunner: QueryRunner): Promise<void> {
// ── STEP 1: Remove all wagons ──────────────────────────────────────────
await queryRunner.query(`DELETE FROM freight.wagons;`);
// ── STEP 2: Ensure all 10 wagon types exist ────────────────────────────
await queryRunner.query(`
INSERT INTO freight.wagon_types (
code,
name,
capacity_tons,
length_meters,
max_wagons_per_train,
supported_load_types,
is_active,
tare_weight_tons
)
VALUES
('NW7', 'Double deck sedan wagon', 22, 26.066, NULL, ARRAY['vehicles', 'sedan'], true, 18.0),
('NW5', 'Flat wagon (container)', 70, 14.000, 53, ARRAY['container', 'steel', 'machinery'], true, 22.0),
('PW2', 'Box wagon', 70, 17.066, 18, ARRAY['general cargo', 'break bulk'], true, 20.0),
('GW2', 'Tank wagon', 70, 12.228, 37, ARRAY['liquid', 'fuel'], true, 25.0),
('CW4', 'Gondola covered wagon', 70, 13.976, 37, ARRAY['covered bulk cargo'], true, 22.0),
('CW3', 'Gondola open wagon', 70, 13.976, NULL, ARRAY['open bulk cargo'], true, 20.0),
('KW2', 'Hopper covered wagon', 69, 16.466, NULL, ARRAY['bulk grains'], true, 22.0),
('KW3', 'Hopper open wagon', 70, 14.400, NULL, ARRAY['coal', 'bulk cargo'], true, 20.0),
('NW6', 'Flat wagon (long cargo)', 70, 18.560, NULL, ARRAY['long cargo'], true, 22.0),
('BW1', 'Refrigerated wagon', 38, 21.996, NULL, ARRAY['refrigerated cargo'], true, 24.0)
ON CONFLICT (code) DO UPDATE SET
name = EXCLUDED.name,
capacity_tons = EXCLUDED.capacity_tons,
length_meters = EXCLUDED.length_meters,
max_wagons_per_train = EXCLUDED.max_wagons_per_train,
supported_load_types = EXCLUDED.supported_load_types,
is_active = true,
tare_weight_tons = EXCLUDED.tare_weight_tons,
deleted_at = NULL,
updated_at = now();
`);
// ── STEP 3: Seed 50 wagons per type across 5 yards ────────────────────
await queryRunner.query(`
DO $$
DECLARE
wt RECORD;
yard_kality UUID;
yard_mojo UUID;
yard_dire_dawa UUID;
yard_djib_port UUID;
yard_nagad UUID;
yards UUID[];
i INT;
yard_id UUID;
wagon_num TEXT;
v_tare NUMERIC;
v_payload NUMERIC;
BEGIN
-- Fetch yard IDs by code (safe across envs — UUIDs differ per DB)
SELECT id INTO yard_kality FROM freight.yards WHERE code = 'KALITY' LIMIT 1;
SELECT id INTO yard_mojo FROM freight.yards WHERE code = 'MOJO' LIMIT 1;
SELECT id INTO yard_dire_dawa FROM freight.yards WHERE code = 'DIRE_DAWA' LIMIT 1;
SELECT id INTO yard_djib_port FROM freight.yards WHERE code = 'DJIB_PORT' LIMIT 1;
SELECT id INTO yard_nagad FROM freight.yards WHERE code = 'NAGAD' LIMIT 1;
IF yard_kality IS NULL OR yard_mojo IS NULL OR yard_dire_dawa IS NULL
OR yard_djib_port IS NULL OR yard_nagad IS NULL
THEN
RAISE EXCEPTION 'One or more operational yards not found. Run the yards seed first.';
END IF;
yards := ARRAY[
yard_kality,
yard_mojo,
yard_dire_dawa,
yard_djib_port,
yard_nagad
];
FOR wt IN
SELECT id, code, capacity_tons, tare_weight_tons
FROM freight.wagon_types
WHERE is_active = true
ORDER BY code
LOOP
v_tare := COALESCE(wt.tare_weight_tons, 20.0);
v_payload := COALESCE(wt.capacity_tons, 60.0);
FOR i IN 1 .. 50 LOOP
wagon_num := wt.code || '-' || LPAD(i::TEXT, 4, '0');
yard_id := yards[ ((i - 1) % 5) + 1 ]; -- round-robin: 1→K, 2→M, 3→D, 4→J, 5→N, 6→K …
INSERT INTO freight.wagons (
id,
wagon_number,
wagon_type_id,
tare_weight,
max_payload_weight,
status,
current_yard_id,
train_id,
sequence_number,
notes,
train_set_wagon_id,
current_train_schedule_id,
created_at,
updated_at
)
VALUES (
uuid_generate_v4(),
wagon_num,
wt.id,
v_tare,
v_payload,
'Available',
yard_id,
NULL, NULL, NULL, NULL, NULL,
now(), now()
)
ON CONFLICT (wagon_number) DO NOTHING;
END LOOP;
RAISE NOTICE 'Seeded 50 wagons for type %.', wt.code;
END LOOP;
END $$;
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Remove all seeded wagons (full wipe — mirrors what up() did)
await queryRunner.query(`DELETE FROM freight.wagons;`);
}
}