refactor: clarify frontend model ownership
Frontend verify / commits (pull_request) Successful in 27s
Frontend verify / security (pull_request) Successful in 9s
Frontend verify / verify (pull_request) Successful in 8m20s

This commit is contained in:
2026-09-28 21:44:53 +03:00
parent 7e976c330c
commit 0865b35f93
44 changed files with 685 additions and 710 deletions
@@ -0,0 +1,152 @@
import type { FC } from "react";
import { Accordion, Alert, Badge, Group, Paper, Stack, Text } from "@mantine/core";
import { useTranslation } from "react-i18next";
import type { BondCoupons as Coupons } from "@/entities/bond";
import { InstrumentSection, type SectionState } from "@/features/instrument-details";
import { formatDate, formatMoney, formatSnapshotTime } from "@/shared/lib";
import { Metric } from "./Metric.view";
type BondCouponsProps = {
coupons?: Coupons;
pastCoupons: Coupons["coupons"];
state: SectionState;
upcoming: Coupons["coupons"];
};
const BondCoupons: FC<BondCouponsProps> = (props) => {
const { coupons, pastCoupons, state, upcoming } = props;
const { t } = useTranslation();
return (
<InstrumentSection
state={state}
title={t("bond.upcomingCoupons")}
>
{!!coupons && (
<Stack gap="md">
{!!(coupons.estimateUnavailable || coupons.positionIsStale) && (
<Alert aria-live="polite" color="orange" role="status">
{t("stock.estimateUnavailable")}
</Alert>
)}
{upcoming.length > 0
? (
upcoming.map(coupon => (
<Coupon
coupon={coupon}
key={`${coupon.number}:${coupon.paymentDate}`}
/>
))
)
: <Text c="dimmed">{t("bond.noCoupons")}</Text>}
<Text c="dimmed" size="xs">
{t("bond.couponWindow", {
from: formatDate(coupons.from),
to: formatDate(coupons.to),
})}
</Text>
<Text c="dimmed" size="xs">
{t("bond.couponNote")}
</Text>
{!!coupons.positionAt && (
<Text c="dimmed" size="xs">
{t("stock.snapshotAt", {
date: formatSnapshotTime(coupons.positionAt),
})}
</Text>
)}
<Accordion radius="md" variant="contained">
<Accordion.Item value="history">
<Accordion.Control>{t("bond.couponHistory")}</Accordion.Control>
<Accordion.Panel>
<Stack gap="sm">
{pastCoupons.length > 0
? (
pastCoupons.map(coupon => (
<Coupon
coupon={coupon}
key={`${coupon.number}:${coupon.paymentDate}`}
/>
))
)
: <Text c="dimmed">{t("bond.noCoupons")}</Text>}
</Stack>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
</Stack>
)}
</InstrumentSection>
);
};
type CouponProps = {
coupon: Coupons["coupons"][number];
};
const Coupon: FC<CouponProps> = (props) => {
const { coupon } = props;
const { t } = useTranslation();
return (
<Paper p="md" radius="md" withBorder>
<Group align="start" justify="space-between">
<div>
<Text fw={600}>
{coupon.paymentDate
? formatDate(coupon.paymentDate)
: t("stock.dateUnknown")}
</Text>
<Text c="dimmed" size="xs">
{t("bond.couponNumber", {
number: coupon.number ?? t("bond.unknown"),
})}
</Text>
</div>
<Metric label={t("bond.perBond")} value={formatMoney(coupon.perBond)} />
{!!coupon.estimatedAmount && (
<Metric
label={t("stock.yourEstimate")}
value={formatMoney(coupon.estimatedAmount)}
/>
)}
</Group>
<Group gap="md" mt="sm">
<Badge color="gray" variant="light">
{t(`bond.couponTypes.${coupon.couponType}`, {
defaultValue: t("bond.unknown"),
})}
</Badge>
{coupon.periodDays != undefined && (
<Text c="dimmed" size="xs">
{t("bond.couponDays", { count: coupon.periodDays })}
</Text>
)}
{!!coupon.recordDate && (
<Text c="dimmed" size="xs">
{t("stock.record")}: {formatDate(coupon.recordDate)}
</Text>
)}
</Group>
</Paper>
);
};
export { BondCoupons };