forked from Yara724/api
YARA-1258
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import { buildInsurerFileReport } from "./case-expert-report.builder";
|
import { buildInsurerFileReport } from "./case-expert-report.builder";
|
||||||
import { PR } from "./persian-report-labels";
|
import { PR } from "./persian-report-labels";
|
||||||
import { toJalaliDateAndTime } from "../helpers/date-jalali";
|
|
||||||
|
|
||||||
describe("buildInsurerFileReport", () => {
|
describe("buildInsurerFileReport", () => {
|
||||||
const getFieldValue = (
|
const getFieldValue = (
|
||||||
@@ -18,9 +17,6 @@ describe("buildInsurerFileReport", () => {
|
|||||||
it("separates insurance blocks and exposes Fanavaran/timeline/evaluation data", () => {
|
it("separates insurance blocks and exposes Fanavaran/timeline/evaluation data", () => {
|
||||||
const fileCreatedAt = new Date("2026-08-10T09:53:23.588Z");
|
const fileCreatedAt = new Date("2026-08-10T09:53:23.588Z");
|
||||||
const evaluationSubmittedAt = new Date("2026-08-10T12:30:45.000Z");
|
const evaluationSubmittedAt = new Date("2026-08-10T12:30:45.000Z");
|
||||||
const [fileDate, fileTime] = toJalaliDateAndTime(fileCreatedAt);
|
|
||||||
const [evaluationDate, evaluationTime] =
|
|
||||||
toJalaliDateAndTime(evaluationSubmittedAt);
|
|
||||||
|
|
||||||
const report = buildInsurerFileReport({
|
const report = buildInsurerFileReport({
|
||||||
overview: {
|
overview: {
|
||||||
@@ -152,6 +148,18 @@ describe("buildInsurerFileReport", () => {
|
|||||||
submittedAt: evaluationSubmittedAt,
|
submittedAt: evaluationSubmittedAt,
|
||||||
description: "نیاز به تعویض سپر جلو",
|
description: "نیاز به تعویض سپر جلو",
|
||||||
actorDetail: { actorName: "کارشناس خسارت نمونه" },
|
actorDetail: { actorName: "کارشناس خسارت نمونه" },
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
partId: 12,
|
||||||
|
carPartDamage: { label_fa: "سپر جلو" },
|
||||||
|
typeOfDamage: "تعویض",
|
||||||
|
price: 2_500_000,
|
||||||
|
salary: 500_000,
|
||||||
|
totalPayment: 3_000_000,
|
||||||
|
factorNeeded: false,
|
||||||
|
daghi: { option: "تحویل داغی" },
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -214,11 +222,11 @@ describe("buildInsurerFileReport", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(getFieldValue(report, PR.timelineSection, PR.fileRegisteredAt)).toBe(
|
expect(getFieldValue(report, PR.timelineSection, PR.fileRegisteredAt)).toBe(
|
||||||
`${fileDate} ${fileTime}`,
|
"1405/05/19 13:23",
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
getFieldValue(report, PR.timelineSection, PR.evaluationRegisteredAt),
|
getFieldValue(report, PR.timelineSection, PR.evaluationRegisteredAt),
|
||||||
).toBe(`${evaluationDate} ${evaluationTime}`);
|
).toBe("1405/05/19 16:00");
|
||||||
|
|
||||||
expect(getFieldValue(report, PR.evaluationSection, PR.evaluationResult)).toBe(
|
expect(getFieldValue(report, PR.evaluationSection, PR.evaluationResult)).toBe(
|
||||||
"تأیید شده",
|
"تأیید شده",
|
||||||
@@ -229,6 +237,91 @@ describe("buildInsurerFileReport", () => {
|
|||||||
expect(getFieldValue(report, PR.evaluationSection, PR.evaluationResponse)).toBe(
|
expect(getFieldValue(report, PR.evaluationSection, PR.evaluationResponse)).toBe(
|
||||||
"نیاز به تعویض سپر جلو",
|
"نیاز به تعویض سپر جلو",
|
||||||
);
|
);
|
||||||
|
expect(getFieldValue(report, "قیمت قطعات", "قطعه ۱ / نام قطعه")).toBe(
|
||||||
|
"سپر جلو",
|
||||||
|
);
|
||||||
|
expect(getFieldValue(report, "قیمت قطعات", "قطعه ۱ / قیمت قطعه")).toBe(
|
||||||
|
"۲٬۵۰۰٬۰۰۰ تومان",
|
||||||
|
);
|
||||||
|
expect(getFieldValue(report, "قیمت قطعات", "قطعه ۱ / مبلغ کل")).toBe(
|
||||||
|
"۳٬۰۰۰٬۰۰۰ تومان",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("localizes legacy English car-body environmental conditions", () => {
|
||||||
|
const report = buildInsurerFileReport({
|
||||||
|
overview: { publicId: "A00012" },
|
||||||
|
blame: {
|
||||||
|
type: "CAR_BODY",
|
||||||
|
parties: [
|
||||||
|
{
|
||||||
|
role: "FIRST",
|
||||||
|
person: { userId: "damaged", fullName: "بیمهگذار" },
|
||||||
|
statement: {
|
||||||
|
weatherCondition: "CLEAR",
|
||||||
|
roadCondition: "wet",
|
||||||
|
lightCondition: "LOW_LIGHT",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getFieldValue(report, PR.accidentSection, PR.weather)).toBe("صاف");
|
||||||
|
expect(getFieldValue(report, PR.accidentSection, PR.road)).toBe("مرطوب");
|
||||||
|
expect(getFieldValue(report, PR.accidentSection, PR.light)).toBe("کمنور");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exposes the damage expert part-price result as a separate section", () => {
|
||||||
|
const report = buildInsurerFileReport({
|
||||||
|
overview: { publicId: "A00013" },
|
||||||
|
claim: {
|
||||||
|
claimStatus: "APPROVED",
|
||||||
|
evaluation: {
|
||||||
|
damageExpertReply: {
|
||||||
|
description: "تعویض قطعه ضروری است",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
carPartDamage: { label_fa: "چراغ جلو" },
|
||||||
|
price: 4_000_000,
|
||||||
|
totalPayment: 4_000_000,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getFieldValue(report, "قیمت قطعات", "قطعه ۱ / نام قطعه")).toBe(
|
||||||
|
"چراغ جلو",
|
||||||
|
);
|
||||||
|
expect(getFieldValue(report, "قیمت قطعات", "قطعه ۱ / قیمت قطعه")).toBe(
|
||||||
|
"۴٬۰۰۰٬۰۰۰ تومان",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to claim history for the evaluation submission time", () => {
|
||||||
|
const report = buildInsurerFileReport({
|
||||||
|
overview: { publicId: "A00014" },
|
||||||
|
claim: {
|
||||||
|
evaluation: {
|
||||||
|
damageExpertReply: { description: "ارزیابی ثبت شد" },
|
||||||
|
},
|
||||||
|
history: [
|
||||||
|
{
|
||||||
|
type: "EXPERT_REPLY_SUBMITTED",
|
||||||
|
timestamp: "2026-08-10T12:30:45.000Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getFieldValue(report, PR.timelineSection, PR.evaluationRegisteredAt),
|
||||||
|
).toBe("1405/05/19 16:00");
|
||||||
|
expect(
|
||||||
|
getFieldValue(report, PR.evaluationSection, PR.evaluationSubmittedAt),
|
||||||
|
).toBe("1405/05/19 16:00");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses the stored V4 licence type for a driver who differs from the policyholder", () => {
|
it("uses the stored V4 licence type for a driver who differs from the policyholder", () => {
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ import {
|
|||||||
InsurerFileReportSection,
|
InsurerFileReportSection,
|
||||||
InsurerFileReportViewModel,
|
InsurerFileReportViewModel,
|
||||||
} from "./case-expert-report.types";
|
} from "./case-expert-report.types";
|
||||||
import { PR, persianFieldPath, persianStatus } from "./persian-report-labels";
|
import {
|
||||||
|
PR,
|
||||||
|
persianAccidentCondition,
|
||||||
|
persianFieldPath,
|
||||||
|
persianStatus,
|
||||||
|
} from "./persian-report-labels";
|
||||||
|
|
||||||
const SKIP_FLATTEN_KEYS = new Set([
|
const SKIP_FLATTEN_KEYS = new Set([
|
||||||
"_id",
|
"_id",
|
||||||
@@ -65,6 +70,32 @@ function formatDateTime(value: unknown): string | undefined {
|
|||||||
return `${d} ${t}`;
|
return `${d} ${t}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PERSIAN_NUMBER_FORMATTER = new Intl.NumberFormat("fa-IR", {
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
function normalizeNumber(value: unknown): number | undefined {
|
||||||
|
if (typeof value === "number") {
|
||||||
|
return Number.isFinite(value) ? value : undefined;
|
||||||
|
}
|
||||||
|
if (typeof value !== "string" || !value.trim()) return undefined;
|
||||||
|
|
||||||
|
const normalized = value
|
||||||
|
.trim()
|
||||||
|
.replace(/[۰-۹]/g, (digit) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(digit)))
|
||||||
|
.replace(/[٠-٩]/g, (digit) => String("٠١٢٣٤٥٦٧٨٩".indexOf(digit)))
|
||||||
|
.replace(/[٬,\s]/g, "");
|
||||||
|
const parsed = Number(normalized);
|
||||||
|
return Number.isFinite(parsed) ? parsed : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatToman(value: unknown): string | undefined {
|
||||||
|
if (value === undefined || value === null || value === "") return undefined;
|
||||||
|
const amount = normalizeNumber(value);
|
||||||
|
if (amount === undefined) return asString(value);
|
||||||
|
return `${PERSIAN_NUMBER_FORMATTER.format(amount)} تومان`;
|
||||||
|
}
|
||||||
|
|
||||||
function firstDefined(...values: unknown[]): string | undefined {
|
function firstDefined(...values: unknown[]): string | undefined {
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
const str = asString(value);
|
const str = asString(value);
|
||||||
@@ -302,6 +333,23 @@ function resolveEvaluationReply(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveEvaluationSubmittedAt(
|
||||||
|
claim: Record<string, unknown> | null | undefined,
|
||||||
|
reply: Record<string, unknown> | undefined,
|
||||||
|
): unknown {
|
||||||
|
if (reply?.submittedAt != null) return reply.submittedAt;
|
||||||
|
|
||||||
|
const history = Array.isArray(claim?.history) ? claim.history : [];
|
||||||
|
const submittedEvent = [...history]
|
||||||
|
.reverse()
|
||||||
|
.find((event) =>
|
||||||
|
["EXPERT_REPLY_SUBMITTED", "EXPERT_FINAL_REPLY_SUBMITTED"].includes(
|
||||||
|
String((event as Record<string, unknown>)?.type ?? ""),
|
||||||
|
),
|
||||||
|
) as Record<string, unknown> | undefined;
|
||||||
|
return submittedEvent?.timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
function insuranceValueFromCandidates(
|
function insuranceValueFromCandidates(
|
||||||
...values: unknown[]
|
...values: unknown[]
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
@@ -650,7 +698,7 @@ function buildCaseTimelineSection(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: PR.evaluationRegisteredAt,
|
label: PR.evaluationRegisteredAt,
|
||||||
value: formatDateTime(evaluationReply?.submittedAt),
|
value: formatDateTime(resolveEvaluationSubmittedAt(claim, evaluationReply)),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -723,7 +771,7 @@ function buildEvaluationSection(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: PR.evaluationSubmittedAt,
|
label: PR.evaluationSubmittedAt,
|
||||||
value: formatDateTime(reply?.submittedAt),
|
value: formatDateTime(resolveEvaluationSubmittedAt(claim, reply)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: PR.evaluationResponse,
|
label: PR.evaluationResponse,
|
||||||
@@ -732,6 +780,66 @@ function buildEvaluationSection(
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function evaluationPartName(part: ReportRecord): string | undefined {
|
||||||
|
const damage = part.carPartDamage;
|
||||||
|
if (damage && typeof damage === "object" && !Array.isArray(damage)) {
|
||||||
|
const record = damage as ReportRecord;
|
||||||
|
return firstDefined(
|
||||||
|
record.label_fa,
|
||||||
|
record.name,
|
||||||
|
record.part,
|
||||||
|
record.label,
|
||||||
|
part.partId != null ? `${PR.part} ${part.partId}` : undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return firstDefined(
|
||||||
|
damage,
|
||||||
|
part.partName,
|
||||||
|
part.partId != null ? `${PR.part} ${part.partId}` : undefined,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function evaluationDaghiValue(value: unknown): string | undefined {
|
||||||
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||||
|
return asString(value);
|
||||||
|
}
|
||||||
|
const daghi = value as ReportRecord;
|
||||||
|
const option = asString(daghi.option);
|
||||||
|
const price = formatToman(daghi.price);
|
||||||
|
return [option, price].filter(Boolean).join(" - ") || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEvaluationPartsSection(
|
||||||
|
claim?: Record<string, unknown> | null,
|
||||||
|
): InsurerFileReportSection | undefined {
|
||||||
|
const reply = resolveEvaluationReply(claim);
|
||||||
|
const parts = Array.isArray(reply?.parts)
|
||||||
|
? (reply.parts as ReportRecord[])
|
||||||
|
: [];
|
||||||
|
if (!parts.length) return undefined;
|
||||||
|
|
||||||
|
const fields = parts.flatMap((part, index) => {
|
||||||
|
const prefix = `${PR.part} ${PERSIAN_NUMBER_FORMATTER.format(index + 1)}`;
|
||||||
|
return [
|
||||||
|
{ label: `${prefix} / ${PR.partName}`, value: evaluationPartName(part) },
|
||||||
|
{ label: `${prefix} / ${PR.damageType}`, value: asString(part.typeOfDamage) },
|
||||||
|
{ label: `${prefix} / ${PR.partPrice}`, value: formatToman(part.price) },
|
||||||
|
{ label: `${prefix} / ${PR.repairSalary}`, value: formatToman(part.salary) },
|
||||||
|
{ label: `${prefix} / ${PR.totalPayment}`, value: formatToman(part.totalPayment) },
|
||||||
|
{
|
||||||
|
label: `${prefix} / ${PR.factorNeeded}`,
|
||||||
|
value:
|
||||||
|
typeof part.factorNeeded === "boolean"
|
||||||
|
? persianStatus(part.factorNeeded)
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
{ label: `${prefix} / ${PR.daghi}`, value: evaluationDaghiValue(part.daghi) },
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return buildSection(PR.evaluationPartsSection, fields, false);
|
||||||
|
}
|
||||||
|
|
||||||
function buildAccidentReportSection(
|
function buildAccidentReportSection(
|
||||||
blame?: Record<string, unknown> | null,
|
blame?: Record<string, unknown> | null,
|
||||||
claim?: Record<string, unknown> | null,
|
claim?: Record<string, unknown> | null,
|
||||||
@@ -773,21 +881,24 @@ function buildAccidentReportSection(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: PR.weather,
|
label: PR.weather,
|
||||||
value:
|
value: persianAccidentCondition(
|
||||||
asString(statement?.weatherCondition) ??
|
"weather",
|
||||||
asString(snapshotAccident?.weatherCondition),
|
statement?.weatherCondition ?? snapshotAccident?.weatherCondition,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: PR.road,
|
label: PR.road,
|
||||||
value:
|
value: persianAccidentCondition(
|
||||||
asString(statement?.roadCondition) ??
|
"road",
|
||||||
asString(snapshotAccident?.roadCondition),
|
statement?.roadCondition ?? snapshotAccident?.roadCondition,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: PR.light,
|
label: PR.light,
|
||||||
value:
|
value: persianAccidentCondition(
|
||||||
asString(statement?.lightCondition) ??
|
"light",
|
||||||
asString(snapshotAccident?.lightCondition),
|
statement?.lightCondition ?? snapshotAccident?.lightCondition,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: PR.blameStatus,
|
label: PR.blameStatus,
|
||||||
@@ -910,6 +1021,7 @@ export function buildInsurerFileReport(file: {
|
|||||||
: undefined,
|
: undefined,
|
||||||
buildFanavaranCodesSection(claim),
|
buildFanavaranCodesSection(claim),
|
||||||
buildEvaluationSection(claim),
|
buildEvaluationSection(claim),
|
||||||
|
buildEvaluationPartsSection(claim),
|
||||||
buildAccidentReportSection(blameContext, claim, damagedParty),
|
buildAccidentReportSection(blameContext, claim, damagedParty),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,15 @@ export const PR = {
|
|||||||
evaluationExpert: "کارشناس ارزیاب",
|
evaluationExpert: "کارشناس ارزیاب",
|
||||||
evaluationSubmittedAt: "تاریخ و ساعت ثبت ارزیابی",
|
evaluationSubmittedAt: "تاریخ و ساعت ثبت ارزیابی",
|
||||||
evaluationResponse: "پاسخ / توضیحات کارشناس",
|
evaluationResponse: "پاسخ / توضیحات کارشناس",
|
||||||
|
evaluationPartsSection: "قیمت قطعات",
|
||||||
|
part: "قطعه",
|
||||||
|
partName: "نام قطعه",
|
||||||
|
damageType: "نوع خسارت",
|
||||||
|
partPrice: "قیمت قطعه",
|
||||||
|
repairSalary: "اجرت تعمیر",
|
||||||
|
totalPayment: "مبلغ کل",
|
||||||
|
factorNeeded: "نیازمند فاکتور",
|
||||||
|
daghi: "داغی",
|
||||||
admitsGuilt: "اقرار به تقصیر",
|
admitsGuilt: "اقرار به تقصیر",
|
||||||
claimsDamage: "ادعای خسارت",
|
claimsDamage: "ادعای خسارت",
|
||||||
acceptsExpertOpinion: "پذیرش نظر کارشناس",
|
acceptsExpertOpinion: "پذیرش نظر کارشناس",
|
||||||
@@ -194,6 +203,32 @@ const STATUS_LABELS: Record<string, string> = {
|
|||||||
false: "خیر",
|
false: "خیر",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const WEATHER_LABELS: Record<string, string> = {
|
||||||
|
clear: "صاف",
|
||||||
|
sunny: "صاف",
|
||||||
|
rainy: "بارانی",
|
||||||
|
rain: "بارانی",
|
||||||
|
snowy: "برفی",
|
||||||
|
snow: "برفی",
|
||||||
|
foggy: "مهآلود",
|
||||||
|
fog: "مهآلود",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ROAD_LABELS: Record<string, string> = {
|
||||||
|
muddy: "گلی",
|
||||||
|
icy: "یخزده",
|
||||||
|
wet: "مرطوب",
|
||||||
|
dry: "خشک",
|
||||||
|
};
|
||||||
|
|
||||||
|
const LIGHT_LABELS: Record<string, string> = {
|
||||||
|
daylight: "روز",
|
||||||
|
day: "روز",
|
||||||
|
night: "شب",
|
||||||
|
low_light: "کمنور",
|
||||||
|
lowlight: "کمنور",
|
||||||
|
};
|
||||||
|
|
||||||
/** Turn `party.insurance.policyNumber` into a Persian label. */
|
/** Turn `party.insurance.policyNumber` into a Persian label. */
|
||||||
export function persianFieldPath(path: string): string {
|
export function persianFieldPath(path: string): string {
|
||||||
if (!path) return PR.data;
|
if (!path) return PR.data;
|
||||||
@@ -231,3 +266,19 @@ export function persianStatus(value: unknown): string | undefined {
|
|||||||
const key = String(value);
|
const key = String(value);
|
||||||
return STATUS_LABELS[key] ?? key;
|
return STATUS_LABELS[key] ?? key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function persianAccidentCondition(
|
||||||
|
kind: "weather" | "road" | "light",
|
||||||
|
value: unknown,
|
||||||
|
): string | undefined {
|
||||||
|
if (value === undefined || value === null || value === "") return undefined;
|
||||||
|
const raw = String(value).trim();
|
||||||
|
const normalized = raw.toLowerCase().replace(/[\s-]+/g, "_");
|
||||||
|
const labels =
|
||||||
|
kind === "weather"
|
||||||
|
? WEATHER_LABELS
|
||||||
|
: kind === "road"
|
||||||
|
? ROAD_LABELS
|
||||||
|
: LIGHT_LABELS;
|
||||||
|
return labels[normalized] ?? raw;
|
||||||
|
}
|
||||||
|
|||||||
17
src/helpers/date-jalali.spec.ts
Normal file
17
src/helpers/date-jalali.spec.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { toJalaliDateAndTime } from "./date-jalali";
|
||||||
|
|
||||||
|
describe("toJalaliDateAndTime", () => {
|
||||||
|
it("always formats timestamps in Asia/Tehran regardless of server timezone", () => {
|
||||||
|
expect(toJalaliDateAndTime("2026-08-10T12:30:45.000Z")).toEqual([
|
||||||
|
"1405/05/19",
|
||||||
|
"16:00",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses the Tehran calendar day when UTC and Tehran dates differ", () => {
|
||||||
|
expect(toJalaliDateAndTime("2026-08-10T21:30:00.000Z")).toEqual([
|
||||||
|
"1405/05/20",
|
||||||
|
"01:00",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { IRAN_TIMEZONE } from "./iran-datetime";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an ISO date string or Date to Jalali (Persian) date and time.
|
* Converts an ISO date string or Date to Jalali (Persian) date and time.
|
||||||
* @param input - ISO date string (e.g. 2026-02-08T13:51:20.747+00:00) or Date
|
* @param input - ISO date string (e.g. 2026-02-08T13:51:20.747+00:00) or Date
|
||||||
@@ -70,6 +72,7 @@ export function jalaliToGregorianDate(
|
|||||||
*/
|
*/
|
||||||
export function toJalaliDate(d: Date): string {
|
export function toJalaliDate(d: Date): string {
|
||||||
const persianDate = d.toLocaleDateString("fa-IR", {
|
const persianDate = d.toLocaleDateString("fa-IR", {
|
||||||
|
timeZone: IRAN_TIMEZONE,
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "2-digit",
|
month: "2-digit",
|
||||||
day: "2-digit",
|
day: "2-digit",
|
||||||
@@ -81,9 +84,12 @@ export function toJalaliDate(d: Date): string {
|
|||||||
* Converts a Date to 24h time string (e.g. 13:37).
|
* Converts a Date to 24h time string (e.g. 13:37).
|
||||||
*/
|
*/
|
||||||
export function toJalaliTime(d: Date): string {
|
export function toJalaliTime(d: Date): string {
|
||||||
const hours = d.getHours().toString().padStart(2, "0");
|
return new Intl.DateTimeFormat("en-GB", {
|
||||||
const minutes = d.getMinutes().toString().padStart(2, "0");
|
timeZone: IRAN_TIMEZONE,
|
||||||
return `${hours}:${minutes}`;
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
hourCycle: "h23",
|
||||||
|
}).format(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user