Add RegistrationPanel component for flight details accordion

This commit is contained in:
2026-04-16 22:25:53 +03:00
parent 37222e07e1
commit f535e4078e
3 changed files with 155 additions and 0 deletions
@@ -0,0 +1,62 @@
// @vitest-environment jsdom
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { RegistrationPanel } from "./RegistrationPanel.js";
import type { IFlightTransitionItem } from "../../types.js";
vi.mock("@/i18n/provider.js", () => ({
useTranslation: () => ({ t: (k: string) => k }),
}));
const baseItem: IFlightTransitionItem = {
start: {
dayChange: { value: 0, title: "" },
local: "10:00",
localTime: "10:00",
tzOffset: 3,
utc: "07:00",
},
end: {
dayChange: { value: 0, title: "" },
local: "10:30",
localTime: "10:30",
tzOffset: 3,
utc: "07:30",
},
status: "InProgress",
isActual: true,
};
describe("RegistrationPanel", () => {
it("renders start and end times", () => {
render(<RegistrationPanel item={baseItem} />);
expect(screen.getByText("10:00")).toBeTruthy();
expect(screen.getByText("10:30")).toBeTruthy();
});
it("renders status label", () => {
render(<RegistrationPanel item={baseItem} />);
expect(screen.getByText("DETAILS.STATUS_IN_PROGRESS")).toBeTruthy();
});
it("renders only start time when end is missing", () => {
const noEnd: IFlightTransitionItem = {
...baseItem,
end: {
dayChange: { value: 0, title: "" },
local: "",
localTime: "",
tzOffset: 0,
utc: "",
},
};
render(<RegistrationPanel item={noEnd} />);
expect(screen.getByText("10:00")).toBeTruthy();
expect(screen.queryByText("10:30")).toBeNull();
});
it("has data-testid for targeting in integration tests", () => {
render(<RegistrationPanel item={baseItem} />);
expect(screen.getByTestId("registration-panel")).toBeTruthy();
});
});
@@ -0,0 +1,40 @@
import type { FC } from "react";
import { useTranslation } from "@/i18n/provider.js";
import type { IFlightTransitionItem, FlightTransitionStatus } from "../../types.js";
import "./panels.scss";
const STATUS_KEYS: Record<FlightTransitionStatus, string> = {
Finished: "DETAILS.STATUS_FINISHED",
Expected: "DETAILS.STATUS_EXPECTED",
InProgress: "DETAILS.STATUS_IN_PROGRESS",
Specified: "DETAILS.STATUS_SPECIFIED",
Scheduled: "DETAILS.STATUS_SCHEDULED",
};
export interface RegistrationPanelProps {
item: IFlightTransitionItem;
}
export const RegistrationPanel: FC<RegistrationPanelProps> = ({ item }) => {
const { t } = useTranslation();
const hasEnd = Boolean(item.end?.local);
return (
<div className="details-panel" data-testid="registration-panel">
<div className="details-panel__row">
<span className="details-panel__label">{t("DETAILS.STATUS")}</span>
<span className="details-panel__status">{t(STATUS_KEYS[item.status])}</span>
</div>
<div className="details-panel__row">
<span className="details-panel__label">{t("DETAILS.SCHEDULED")}</span>
<span className="details-panel__value">{item.start.local}</span>
</div>
{hasEnd && (
<div className="details-panel__row">
<span className="details-panel__label">{t("DETAILS.ACTUAL")}</span>
<span className="details-panel__value">{item.end.local}</span>
</div>
)}
</div>
);
};
@@ -0,0 +1,53 @@
.details-panel {
padding: 12px 0;
font-size: 14px;
line-height: 1.5;
}
.details-panel__row {
display: flex;
justify-content: space-between;
padding: 6px 0;
border-bottom: 1px solid #eee;
&:last-child {
border-bottom: none;
}
}
.details-panel__label {
color: #666;
}
.details-panel__value {
color: #000;
font-weight: 500;
}
.details-panel__status {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
background: #f0f4f8;
color: #2060c0;
font-size: 12px;
}
.details-panel__icons {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.details-panel__icon {
display: inline-flex;
align-items: center;
gap: 6px;
text-decoration: none;
color: inherit;
img {
width: 24px;
height: 24px;
}
}