refactor: enforce frontend view model boundaries
This commit is contained in:
@@ -17,6 +17,7 @@ import { formatDate, formatMoney } from "@/shared/lib";
|
||||
const PAGE_SIZES = new Set(["5", "10", "20", "50", "100", "all"]);
|
||||
|
||||
type CardPageVm = ReturnType<typeof useCardPageVm>;
|
||||
|
||||
function useCardPageVm() {
|
||||
const { cardId } = useParams();
|
||||
const [parameters, setParameters] = useSearchParams();
|
||||
@@ -25,32 +26,46 @@ function useCardPageVm() {
|
||||
const requestedSize = parameters.get("limit") ?? "10";
|
||||
const pageSize = PAGE_SIZES.has(requestedSize) ? requestedSize : "10";
|
||||
const rawPage = Number(parameters.get("page") ?? 1);
|
||||
|
||||
const page
|
||||
= pageSize !== "all" && Number.isSafeInteger(rawPage) && rawPage > 0
|
||||
? rawPage
|
||||
: 1;
|
||||
|
||||
const card = useBankCard(cardId);
|
||||
const { updateCard } = useBankMutations();
|
||||
const [archiveError, setArchiveError] = useState<unknown>();
|
||||
const cards = useBankCards(true);
|
||||
|
||||
const history = useBankOperations(cardId, {
|
||||
...period,
|
||||
limit: pageSize === "all" ? "all" : Number(pageSize),
|
||||
page,
|
||||
});
|
||||
|
||||
const summary = useBankSummary(period, cardId);
|
||||
const [editingCard, setEditingCard] = useState(false);
|
||||
const [operation, setOperation] = useState<"new" | BankOperation | null>(
|
||||
|
||||
const [operation, setOperation] = useState<"new"
|
||||
| BankOperation
|
||||
| null>(
|
||||
null,
|
||||
);
|
||||
|
||||
const [deleting, setDeleting] = useState<BankOperation | null>(null);
|
||||
|
||||
const rows = history.data?.items.map((item) => {
|
||||
const isOutgoing
|
||||
= item.kind === "expense"
|
||||
|| (item.kind === "transfer" && item.cardId === cardId);
|
||||
|
||||
const otherCardId = isOutgoing ? item.targetCardId : item.cardId;
|
||||
let title = t(`bank.kinds.${item.kind}`);
|
||||
if (item.kind === "transfer") title = t(isOutgoing ? "bank.transferTo" : "bank.transferFrom", { name: isOutgoing ? item.targetCardName : item.cardName });
|
||||
if (item.kind === "transfer")
|
||||
title = t(isOutgoing ? "bank.transferTo" : "bank.transferFrom", {
|
||||
name: isOutgoing ? item.targetCardName : item.cardName,
|
||||
});
|
||||
|
||||
return {
|
||||
color: isOutgoing ? "red" : "green",
|
||||
editable:
|
||||
@@ -64,24 +79,34 @@ function useCardPageVm() {
|
||||
metadata: [formatDate(item.date), item.categoryName, item.description]
|
||||
.filter(Boolean)
|
||||
.join(" · "),
|
||||
onDelete: () => setDeleting(item),
|
||||
onEdit: () => setOperation(item),
|
||||
otherCard:
|
||||
item.kind === "transfer"
|
||||
? otherCardId
|
||||
: /* eslint-disable-line unicorn/no-null -- Preserve the nullable domain value consumed by this view model. */ null,
|
||||
: null, // eslint-disable-line unicorn/no-null -- Preserve the nullable domain value consumed by this view model.
|
||||
title,
|
||||
tone: isOutgoing ? ("negative" as const) : ("positive" as const),
|
||||
value: `${isOutgoing ? "−" : "+"}${formatMoney(item.amount)}`,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
archiveError,
|
||||
archivePending: updateCard.isPending,
|
||||
card,
|
||||
cards,
|
||||
closeDeletion: () => setDeleting(null),
|
||||
closeEditing: () => setEditingCard(false),
|
||||
closeOperation: () => setOperation(null),
|
||||
deleting,
|
||||
editableOperation: operation === "new" ? undefined : (operation ?? undefined),
|
||||
editingCard,
|
||||
history,
|
||||
notFound: isApiErrorCode(apiErrorCode(card.error), ApiErrorCode.BankNotFound),
|
||||
notFound: isApiErrorCode(
|
||||
apiErrorCode(card.error),
|
||||
ApiErrorCode.BankNotFound,
|
||||
),
|
||||
operation,
|
||||
page,
|
||||
pageSize,
|
||||
@@ -104,6 +129,7 @@ function useCardPageVm() {
|
||||
toggleArchive: async () => {
|
||||
if (!card.data) return;
|
||||
setArchiveError(undefined);
|
||||
|
||||
try {
|
||||
await updateCard.mutateAsync({
|
||||
body: { archived: !card.data.archivedAt, version: card.data.version },
|
||||
|
||||
Reference in New Issue
Block a user