feat: polish bank operation cards and dates
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
bankCardSchema,
|
||||
bankDateInputValue,
|
||||
bankDateSchema,
|
||||
bankMoneyValue,
|
||||
bankMonth,
|
||||
@@ -54,3 +55,15 @@ describe("bank form input", () => {
|
||||
expect(bankDateSchema.safeParse("2026-02-30").success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it("converts local date input without accepting impossible or unfinished dates", () => {
|
||||
expect(bankDateInputValue("07.08.2026")).toBe("2026-08-07");
|
||||
expect(
|
||||
bankDateSchema.safeParse(bankDateInputValue("29.02.2024")).success,
|
||||
).toBe(true);
|
||||
for (const value of ["31.02.2026", "07.08.", ""]) {
|
||||
expect(bankDateSchema.safeParse(bankDateInputValue(value)).success).toBe(
|
||||
false,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,6 +9,9 @@ function bankToday() {
|
||||
day: "2-digit",
|
||||
}).format(new Date());
|
||||
}
|
||||
function bankDateInputValue(value: string) {
|
||||
return value.replace(/^(\d{2})\.(\d{2})\.(\d{4})$/, "$3-$2-$1");
|
||||
}
|
||||
function bankMonth(value: string | null) {
|
||||
return value && /^\d{4}-(0[1-9]|1[0-2])$/.test(value)
|
||||
? value
|
||||
@@ -76,6 +79,7 @@ type BankCardValues = z.infer<typeof bankCardSchema>;
|
||||
export {
|
||||
type BankCardValues,
|
||||
bankCardSchema,
|
||||
bankDateInputValue,
|
||||
bankDateSchema,
|
||||
bankMoneyValue,
|
||||
bankMonth,
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import type { BankOperation } from "../api/queries";
|
||||
|
||||
type OperationIcon = { path: string; color: string };
|
||||
|
||||
const categoryIcons: Record<string, OperationIcon> = {
|
||||
алкоголь: {
|
||||
path: "M7 3h10v5a5 5 0 0 1-10 0V3Z M7 7h10 M12 13v8 M8 21h8",
|
||||
color: "grape",
|
||||
},
|
||||
вклады: {
|
||||
path: "M4 8c0-4 16-4 16 0s-16 4-16 0Z M4 8v5c0 4 16 4 16 0V8 M4 13v5c0 4 16 4 16 0v-5",
|
||||
color: "green",
|
||||
},
|
||||
"инвестиции и накопления": {
|
||||
path: "M3 3v18h18 M6 15l5-5 4 3 6-8 M16 5h5v5",
|
||||
color: "green",
|
||||
},
|
||||
образование: {
|
||||
path: "M2 8l10-5 10 5-10 5L2 8Z M6 10v7c4 3 8 3 12 0v-7 M22 8v9",
|
||||
color: "blue",
|
||||
},
|
||||
переводы: { path: "M3 7h18l-4-4 M21 17H3l4 4", color: "blue" },
|
||||
"погашение кредита": {
|
||||
path: "M3 5h18v14H3V5Z M3 9h18 M8 14l3 3 5-5",
|
||||
color: "orange",
|
||||
},
|
||||
подписки: {
|
||||
path: "M4 10a8 8 0 0 1 13-6l3 3 M20 3v4h-4 M20 14a8 8 0 0 1-13 6l-3-3 M4 21v-4h4",
|
||||
color: "grape",
|
||||
},
|
||||
путешествия: {
|
||||
path: "M10 3h4v7l7 5v3l-7-3v5l2 1v1l-4-1-4 1v-1l2-1v-5l-7 3v-3l7-5V3Z",
|
||||
color: "blue",
|
||||
},
|
||||
"связь и интернет": {
|
||||
path: "M3 7a14 14 0 0 1 18 0 M6 11a9 9 0 0 1 12 0 M9 15a4 4 0 0 1 6 0 M12 19h.01",
|
||||
color: "blue",
|
||||
},
|
||||
"снятие наличных": {
|
||||
path: "M4 10V3h16v7 M7 7h10v14H7V7Z M7 11h10 M10 16h4",
|
||||
color: "green",
|
||||
},
|
||||
ставки: {
|
||||
path: "M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z M7 7h.01 M17 7h.01 M12 12h.01 M7 17h.01 M17 17h.01",
|
||||
color: "grape",
|
||||
},
|
||||
страхование: {
|
||||
path: "M12 2l8 4v6c0 5-8 10-8 10S4 17 4 12V6l8-4Z M8 12l3 3 5-6",
|
||||
color: "green",
|
||||
},
|
||||
табак: {
|
||||
path: "M3 14h18v5H3v-5Z M16 14v5 M8 10V8c0-2 3-2 3-4V2 M15 10V8c0-2 3-2 3-4",
|
||||
color: "orange",
|
||||
},
|
||||
"хостинг и ии-сервисы": {
|
||||
path: "M3 3h18v7H3V3Z M3 14h18v7H3v-7Z M7 6.5h.01 M7 17.5h.01 M15 6.5h3 M15 17.5h3",
|
||||
color: "grape",
|
||||
},
|
||||
"цифровые покупки": {
|
||||
path: "M3 3h18v13H3V3Z M8 21h8 M12 16v5 M12 6v7 M9 10l3 3 3-3",
|
||||
color: "blue",
|
||||
},
|
||||
продукты: {
|
||||
path: "M3 9h18l-2 11H5L3 9Z M8 9l4-6 4 6 M9 13v3 M15 13v3",
|
||||
color: "green",
|
||||
},
|
||||
кафе: {
|
||||
path: "M4 8h12v7a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4V8Z M16 9h2a3 3 0 0 1 0 6h-2 M8 3v2 M12 3v2 M3 22h15",
|
||||
color: "orange",
|
||||
},
|
||||
такси: {
|
||||
path: "M4 10l2-6h12l2 6 M3 10h18v8H3v-8Z M6 18v3 M18 18v3 M6 14h2 M16 14h2 M10 4V2h4v2",
|
||||
color: "yellow",
|
||||
},
|
||||
транспорт: {
|
||||
path: "M6 3h12a2 2 0 0 1 2 2v13H4V5a2 2 0 0 1 2-2Z M4 11h16 M7 15h1 M16 15h1 M7 18v3 M17 18v3 M9 6h6",
|
||||
color: "blue",
|
||||
},
|
||||
жилье: { path: "M3 11l9-8 9 8 M5 10v11h14V10 M9 21v-8h6v8", color: "orange" },
|
||||
здоровье: { path: "M9 3h6v6h6v6h-6v6H9v-6H3V9h6V3Z", color: "red" },
|
||||
покупки: {
|
||||
path: "M5 7h14l2 14H3L5 7Z M8 8V6a4 4 0 0 1 8 0v2",
|
||||
color: "grape",
|
||||
},
|
||||
развлечения: {
|
||||
path: "M9 5v14a3 3 0 1 1-3-3h3 M9 5l11-2v14a3 3 0 1 1-3-3h3 M9 9l11-2",
|
||||
color: "grape",
|
||||
},
|
||||
};
|
||||
const otherIcon = {
|
||||
path: "M4 4h6v6H4V4Z M14 4h6v6h-6V4Z M4 14h6v6H4v-6Z M14 14h6v6h-6v-6Z",
|
||||
color: "gray",
|
||||
};
|
||||
const operationIcons = {
|
||||
topup: {
|
||||
path: "M20 8V5H5a2 2 0 0 1 0-4h13v4 M3 3v16a2 2 0 0 0 2 2h15V8H5 M16 12v5 M13.5 14.5h5",
|
||||
color: "green",
|
||||
},
|
||||
transfer: { path: "M3 7h18l-4-4 M21 17H3l4 4", color: "blue" },
|
||||
refund: { path: "M8 4L3 9l5 5 M3 9h11a7 7 0 0 1 0 14", color: "teal" },
|
||||
};
|
||||
|
||||
function bankOperationAppearance(
|
||||
kind: BankOperation["kind"],
|
||||
categoryName?: string | null,
|
||||
) {
|
||||
const name = categoryName
|
||||
?.trim()
|
||||
.toLocaleLowerCase("ru-RU")
|
||||
.replaceAll("ё", "е");
|
||||
return kind === "expense"
|
||||
? name && Object.hasOwn(categoryIcons, name)
|
||||
? categoryIcons[name]
|
||||
: otherIcon
|
||||
: operationIcons[kind];
|
||||
}
|
||||
|
||||
export { bankOperationAppearance };
|
||||
Reference in New Issue
Block a user