You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

721 lines
30 KiB

"use client";
import { useEffect, useMemo, useRef, useState } from "react";
import type { QuestionField } from "@/data/question-data";
import { useI18n } from "@/translations/provider";
import { useQuestionAnswers } from "./question-answer-storage";
import QuestionTitle from "./question-title";
type QuestionNumberProps = {
question: QuestionField;
questionIndex: number;
disabled?: boolean;
derivedFromQuestion?: QuestionField;
derivedFromQuestionIndex?: number;
};
const NUMBER_INPUT_PATTERN = /^-?\d*\.?\d*$/;
export default function QuestionNumber({
question,
questionIndex,
disabled,
derivedFromQuestion,
derivedFromQuestionIndex,
}: QuestionNumberProps) {
const { dictionary: t, locale } = useI18n();
const { getAnswerValue, setAnswerValue } = useQuestionAnswers();
const value = getAnswerValue(question, questionIndex);
const derivedValue =
derivedFromQuestion && derivedFromQuestionIndex !== undefined
? getAnswerValue(derivedFromQuestion, derivedFromQuestionIndex)
: null;
useEffect(() => {
if (derivedFromQuestion && typeof derivedValue === "string") {
const age = calculateAge(derivedValue);
if (age !== String(value)) {
setAnswerValue(question, questionIndex, age);
}
}
}, [
derivedFromQuestion,
derivedValue,
question,
questionIndex,
setAnswerValue,
value,
]);
useEffect(() => {
if (
typeof value === "string" &&
value.length > 0 &&
!NUMBER_INPUT_PATTERN.test(value)
) {
setAnswerValue(question, questionIndex, null);
}
}, [question, questionIndex, setAnswerValue, value]);
const [min, max] = question.extras.range;
const numValue =
typeof value === "number"
? value
: typeof value === "string"
? parseFloat(value)
: NaN;
const isOutOfRange = useMemo(() => {
if (Number.isNaN(numValue)) return false;
if (min !== 0 && numValue < min) return true;
if (max !== 0 && numValue > max) return true;
return false;
}, [numValue, min, max]);
const rawInputValue = value == null ? "" : String(value);
const inputValue = NUMBER_INPUT_PATTERN.test(rawInputValue)
? rawInputValue
: "";
const isMonthlyIncome =
question.title === "Monthly Income" ||
question.title === "میزان درآمد ماهانه";
const countryName = useMemo(() => getCountryFromStorage(), []);
const [currencyCode, setCurrencyCode] = useState(() => {
if (typeof window !== "undefined") {
const stored = window.localStorage.getItem("marriage:income:currency");
if (stored) return stored;
}
return getCurrencyForCountry(countryName);
});
const [isCurrencyDropdownOpen, setIsCurrencyDropdownOpen] = useState(false);
const [currencySearchQuery, setCurrencySearchQuery] = useState("");
const currencyContainerRef = useRef<HTMLDivElement>(null);
const currencySearchInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (typeof window !== "undefined") {
const stored = window.localStorage.getItem("marriage:income:currency");
if (stored) {
setCurrencyCode(stored);
return;
}
}
const derived = getCurrencyForCountry(countryName);
setCurrencyCode(derived);
}, [countryName]);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (
currencyContainerRef.current &&
!currencyContainerRef.current.contains(event.target as Node)
) {
setIsCurrencyDropdownOpen(false);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);
useEffect(() => {
if (isCurrencyDropdownOpen) {
setTimeout(() => {
currencySearchInputRef.current?.focus();
}, 50);
}
}, [isCurrencyDropdownOpen]);
const filteredCurrencies = useMemo(() => {
const q = currencySearchQuery.toLowerCase().trim();
if (!q) return CURRENCIES;
return CURRENCIES.filter(
(c) =>
c.code.toLowerCase().includes(q) ||
c.nameEn.toLowerCase().includes(q) ||
c.nameFa.toLowerCase().includes(q),
);
}, [currencySearchQuery]);
const placeholderCurrency = useMemo(() => {
if (currencyCode === "TOMAN") {
return locale === "fa" ? "تومان" : "TOMAN";
}
return currencyCode;
}, [currencyCode, locale]);
const dynamicPlaceholder = useMemo(() => {
if (!isMonthlyIncome) {
return question.extras.placeHolder;
}
return locale === "fa"
? `مثال: ۴۰۰۰ ${placeholderCurrency}`
: `e.g. 4000 ${placeholderCurrency}`;
}, [
isMonthlyIncome,
question.extras.placeHolder,
placeholderCurrency,
locale,
]);
const [localTextValue, setLocalTextValue] = useState(() =>
formatNumberWithCommas(inputValue),
);
useEffect(() => {
const formatted = formatNumberWithCommas(
value == null ? "" : String(value),
);
const cleanLocal = localTextValue.replace(/,/g, "");
const cleanFormatted = formatted.replace(/,/g, "");
if (cleanFormatted !== cleanLocal) {
setLocalTextValue(formatted);
}
}, [value, localTextValue]);
if (isMonthlyIncome) {
return (
<div
className={[
"flex w-full flex-col gap-2 transition-opacity duration-200",
disabled ? "pointer-events-none opacity-30" : "",
].join(" ")}
>
<QuestionTitle question={question} />
<div className="flex gap-3 w-full relative" ref={currencyContainerRef}>
<div className="flex-1 min-w-0">
<input
type="text"
inputMode="decimal"
required={question.required && !disabled}
disabled={disabled || Boolean(derivedFromQuestion)}
placeholder={dynamicPlaceholder}
value={localTextValue}
onChange={(event) => {
const nextValue = event.target.value;
const cleanValue = nextValue.replace(/,/g, "");
if (
cleanValue !== "" &&
!NUMBER_INPUT_PATTERN.test(cleanValue)
) {
return;
}
const formatted = formatNumberWithCommas(cleanValue);
const finalFormatted = nextValue.endsWith(".")
? `${formatted}.`
: formatted;
setLocalTextValue(finalFormatted);
if (cleanValue === "") {
setAnswerValue(question, questionIndex, null);
} else {
const parsed = parseFloat(cleanValue);
setAnswerValue(
question,
questionIndex,
Number.isNaN(parsed) ? cleanValue : parsed,
);
}
}}
className={[
"h-[54px] w-full rounded-[16px] border px-4.5 text-[15px] font-medium text-[#181818] outline-none transition-all placeholder:text-[#98A2B3] focus:border-[#6F6F6F] focus:ring-1 focus:ring-[#6F6F6F] disabled:bg-[#F5F2F1] disabled:text-[#7C7472]",
isOutOfRange
? "border-[#F2465F] ring-1 ring-[#F2465F]"
: "border-[#D0D5DD] hover:border-[#98A2B3] bg-white",
].join(" ")}
/>
</div>
<div className="w-[110px] shrink-0 relative">
<button
type="button"
disabled={disabled}
onClick={() => setIsCurrencyDropdownOpen(!isCurrencyDropdownOpen)}
className={[
"flex h-[54px] w-full items-center justify-between rounded-[16px] border bg-white px-3.5 text-start transition-all cursor-pointer outline-none",
isCurrencyDropdownOpen
? "border-[#6F6F6F] ring-1 ring-[#6F6F6F]"
: "border-[#D0D5DD] hover:border-[#98A2B3]",
].join(" ")}
>
<span className="text-[15px] font-medium text-[#181818] truncate">
{placeholderCurrency}
</span>
<svg
width="12"
height="8"
viewBox="0 0 16 10"
fill="none"
role="img"
aria-label="Dropdown chevron"
className={[
"shrink-0 transition-transform duration-200 text-[#344054] ml-1",
isCurrencyDropdownOpen ? "rotate-180" : "",
].join(" ")}
>
<path
d="M14.75 1.25L7.75 8.25L0.75 1.25"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
{isCurrencyDropdownOpen && (
<div className="absolute top-[calc(100%+8px)] right-0 z-50 flex w-[240px] flex-col gap-3 rounded-[20px] bg-white p-4 shadow-[0_12px_36px_rgba(0,0,0,0.09)] border border-[#EAECF0] animate-in fade-in zoom-in-95 duration-150">
{/* Search bar */}
<div className="flex h-[40px] w-full items-center gap-2 rounded-[10px] bg-[#EFEFEF] px-3 transition-colors focus-within:bg-[#E8E8E8]">
<svg
width="16"
height="16"
viewBox="0 0 18 18"
fill="none"
role="img"
aria-label="Search"
className="shrink-0 text-[#667085]"
>
<path
d="M8.25 14.25C11.5637 14.25 14.25 11.5637 14.25 8.25C14.25 4.93629 11.5637 2.25 8.25 2.25C4.93629 2.25 2.25 4.93629 2.25 8.25C2.25 11.5637 4.93629 14.25 8.25 14.25Z"
stroke="#667085"
strokeWidth="1.75"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M15.75 15.75L12.5 12.5"
stroke="#667085"
strokeWidth="1.75"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
<input
ref={currencySearchInputRef}
type="text"
value={currencySearchQuery}
onChange={(e) => setCurrencySearchQuery(e.target.value)}
placeholder={locale === "fa" ? "جستجو..." : "Search..."}
className="flex-1 min-w-0 bg-transparent text-[13px] font-medium text-[#181818] outline-none placeholder:text-[#98A2B3]"
/>
{currencySearchQuery ? (
<button
type="button"
onClick={() => setCurrencySearchQuery("")}
className="text-[#667085] hover:text-[#181818] text-xs font-semibold cursor-pointer shrink-0"
>
</button>
) : null}
</div>
{/* Options list */}
<div className="flex max-h-[180px] flex-col gap-2.5 overflow-y-auto overscroll-contain pr-1">
{filteredCurrencies.length > 0 ? (
filteredCurrencies.map((c) => {
const isSelected = c.code === currencyCode;
return (
<button
key={c.code}
type="button"
onClick={() => {
setCurrencyCode(c.code);
if (typeof window !== "undefined") {
window.localStorage.setItem(
"marriage:income:currency",
c.code,
);
}
setIsCurrencyDropdownOpen(false);
setCurrencySearchQuery("");
}}
className="flex w-full items-center justify-between text-start cursor-pointer group/opt py-1 px-1.5 rounded-lg hover:bg-gray-50 transition-colors"
>
<span className="text-[14px] font-medium text-[#181818]">
{c.code}{" "}
{locale === "fa"
? `(${c.nameFa})`
: `(${c.nameEn})`}
</span>
{isSelected ? (
<div className="size-[6px] rounded-full bg-[#F2465F]" />
) : null}
</button>
);
})
) : (
<span className="py-2 text-[12px] text-[#667085] text-center">
{locale === "fa"
? "ارزی یافت نشد"
: "No currencies found"}
</span>
)}
</div>
</div>
)}
</div>
</div>
{isOutOfRange ? (
<span className="block text-[10px] font-semibold text-[#F2465F]">
{t.common.rangeError ||
`Please enter a value between ${min} and ${max}`}
</span>
) : null}
</div>
);
}
return (
<div
className={[
"flex w-full flex-col gap-2 transition-opacity duration-200",
disabled ? "pointer-events-none opacity-30" : "",
].join(" ")}
>
<QuestionTitle question={question} />
<input
type="number"
required={question.required && !disabled}
disabled={disabled || Boolean(derivedFromQuestion)}
min={min || undefined}
max={max || undefined}
placeholder={question.extras.placeHolder}
value={inputValue}
onChange={(event) => {
const nextValue = event.target.value;
if (!NUMBER_INPUT_PATTERN.test(nextValue)) {
event.currentTarget.value = inputValue;
return;
}
if (nextValue === "") {
setAnswerValue(question, questionIndex, null);
} else {
const parsed = parseFloat(nextValue);
setAnswerValue(
question,
questionIndex,
Number.isNaN(parsed) ? nextValue : parsed,
);
}
}}
className={[
"h-[54px] w-full rounded-[16px] border px-4.5 text-[15px] font-medium text-[#181818] outline-none transition-all placeholder:text-[#98A2B3] focus:border-[#6F6F6F] focus:ring-1 focus:ring-[#6F6F6F] disabled:bg-[#F5F2F1] disabled:text-[#7C7472]",
isOutOfRange
? "border-[#F2465F] ring-1 ring-[#F2465F]"
: "border-[#D0D5DD] hover:border-[#98A2B3] bg-white",
].join(" ")}
/>
{isOutOfRange ? (
<span className="block text-[10px] font-semibold text-[#F2465F]">
{t.common.rangeError ||
`Please enter a value between ${min} and ${max}`}
</span>
) : null}
</div>
);
}
function calculateAge(dateOfBirth: string) {
const birthDate = new Date(dateOfBirth);
if (Number.isNaN(birthDate.getTime())) {
return "";
}
const today = new Date();
let age = today.getFullYear() - birthDate.getFullYear();
const monthDifference = today.getMonth() - birthDate.getMonth();
if (
monthDifference < 0 ||
(monthDifference === 0 && today.getDate() < birthDate.getDate())
) {
age -= 1;
}
return String(Math.max(age, 0));
}
function getCountryFromStorage(): string {
if (typeof window === "undefined") return "";
try {
const rawValue = window.localStorage.getItem(
"marriage:sections:contact_residence_family_communication:answers",
);
if (!rawValue) return "";
const storedValue = JSON.parse(rawValue);
const field = storedValue.fields?.find(
(f: { type?: string; key?: string; value?: unknown }) =>
f.type === "birthplace" ||
f.key?.includes("current_residence") ||
f.key?.includes("mhl_skwnt_fly"),
);
const value = field?.value;
if (typeof value === "string") {
const parts = value.split(",");
return parts[0]?.trim() || "";
}
} catch {
// Ignore
}
return "";
}
function getCurrencyForCountry(countryName: string): string {
const cleanCountry = countryName?.trim();
if (!cleanCountry) return "USD";
const countryMap: Record<string, string> = {
// English
Iran: "TOMAN",
"United States": "USD",
"United Kingdom": "GBP",
Canada: "CAD",
Germany: "EUR",
France: "EUR",
"United Arab Emirates": "AED",
Turkey: "TRY",
Iraq: "IQD",
Afghanistan: "AFN",
Pakistan: "PKR",
"Saudi Arabia": "SAR",
Qatar: "QAR",
Sweden: "SEK",
Netherlands: "EUR",
Norway: "NOK",
Australia: "AUD",
Bulgaria: "BGN",
// Persian
ایران: "TOMAN",
"ایالات متحده": "USD",
بریتانیا: "GBP",
کانادا: "CAD",
آلمان: "EUR",
فرانسه: "EUR",
"امارات متحده عربی": "AED",
ترکیه: "TRY",
عراق: "IQD",
افغانستان: "AFN",
پاکستان: "PKR",
"عربستان سعودی": "SAR",
قطر: "QAR",
سوئد: "SEK",
هلند: "EUR",
نروژ: "NOK",
استرالیا: "AUD",
بلغارستان: "BGN",
};
return countryMap[cleanCountry] || "USD";
}
const CURRENCIES = [
{ code: "AED", nameEn: "UAE Dirham", nameFa: "درهم امارات" },
{ code: "AFN", nameEn: "Afghan Afghani", nameFa: "افغانی افغانستان" },
{ code: "ALL", nameEn: "Albanian Lek", nameFa: "لک آلبانی" },
{ code: "AMD", nameEn: "Armenian Dram", nameFa: "درام ارمنستان" },
{
code: "ANG",
nameEn: "Netherlands Antillean Guilder",
nameFa: "گیلدر آنتیل هلند",
},
{ code: "AOA", nameEn: "Angolan Kwanza", nameFa: "کوانزای آنگولا" },
{ code: "ARS", nameEn: "Argentine Peso", nameFa: "پزو آرژانتین" },
{ code: "AUD", nameEn: "Australian Dollar", nameFa: "دلار استرالیا" },
{ code: "AZN", nameEn: "Azerbaijani Manat", nameFa: "منات آذربایجان" },
{
code: "BAM",
nameEn: "Bosnia-Herzegovina Mark",
nameFa: "مارک بوسنی و هرزگوین",
},
{ code: "BBD", nameEn: "Barbadian Dollar", nameFa: "دلار باربادوس" },
{ code: "BDT", nameEn: "Bangladeshi Taka", nameFa: "تاکای بنگلادش" },
{ code: "BGN", nameEn: "Bulgarian Lev", nameFa: "لو بلغارستان" },
{ code: "BHD", nameEn: "Bahraini Dinar", nameFa: "دینار بحرین" },
{ code: "BIF", nameEn: "Burundian Franc", nameFa: "فرانک بروندی" },
{ code: "BMD", nameEn: "Bermudian Dollar", nameFa: "دلار برمودا" },
{ code: "BND", nameEn: "Brunei Dollar", nameFa: "دلار برونئی" },
{ code: "BOB", nameEn: "Bolivian Boliviano", nameFa: "بولیویانو بولیوی" },
{ code: "BRL", nameEn: "Brazilian Real", nameFa: "رئال برزیل" },
{ code: "BSD", nameEn: "Bahamian Dollar", nameFa: "دلار باهاما" },
{ code: "BTN", nameEn: "Bhutanese Ngultrum", nameFa: "نگولتروم بوتان" },
{ code: "BWP", nameEn: "Botswanan Pula", nameFa: "پولای بوتسوانا" },
{ code: "BYN", nameEn: "Belarusian Ruble", nameFa: "روبل بلاروس" },
{ code: "BZD", nameEn: "Belize Dollar", nameFa: "دلار بلیز" },
{ code: "CAD", nameEn: "Canadian Dollar", nameFa: "دلار کانادا" },
{ code: "CDF", nameEn: "Congolese Franc", nameFa: "فرانک کنگو" },
{ code: "CHF", nameEn: "Swiss Franc", nameFa: "فرانک سوئیس" },
{ code: "CLP", nameEn: "Chilean Peso", nameFa: "پزو شیلی" },
{ code: "CNY", nameEn: "Chinese Yuan", nameFa: "یوان چین" },
{ code: "COP", nameEn: "Colombian Peso", nameFa: "پزو کلمبیا" },
{ code: "CRC", nameEn: "Costa Rican Colón", nameFa: "کولون کاستاریکا" },
{ code: "CUP", nameEn: "Cuban Peso", nameFa: "پزو کوبا" },
{ code: "CVE", nameEn: "Cape Verdean Escudo", nameFa: "اسکودو کیپ ورد" },
{ code: "CZK", nameEn: "Czech Koruna", nameFa: "کرون چک" },
{ code: "DJF", nameEn: "Djiboutian Franc", nameFa: "فرانک جیبوتی" },
{ code: "DKK", nameEn: "Danish Krone", nameFa: "کرون دانمارک" },
{ code: "DOP", nameEn: "Dominican Peso", nameFa: "پزو دومینیکن" },
{ code: "DZD", nameEn: "Algerian Dinar", nameFa: "دینار الجزایر" },
{ code: "EGP", nameEn: "Egyptian Pound", nameFa: "پوند مصر" },
{ code: "ERN", nameEn: "Eritrean Nakfa", nameFa: "ناکفای اریتره" },
{ code: "ETB", nameEn: "Ethiopian Birr", nameFa: "بیر اتیوپی" },
{ code: "EUR", nameEn: "Euro", nameFa: "یورو" },
{ code: "FJD", nameEn: "Fijian Dollar", nameFa: "دلار فیجی" },
{
code: "FKP",
nameEn: "Falkland Islands Pound",
nameFa: "پوند جزایر فالکلند",
},
{ code: "GBP", nameEn: "British Pound", nameFa: "پوند بریتانیا" },
{ code: "GEL", nameEn: "Georgian Lari", nameFa: "لاری گرجستان" },
{ code: "GHS", nameEn: "Ghanaian Cedi", nameFa: "سدی غنا" },
{ code: "GIP", nameEn: "Gibraltar Pound", nameFa: "پوند جبل الطارق" },
{ code: "GMD", nameEn: "Gambian Dalasi", nameFa: "دالاسی گامبیا" },
{ code: "GNF", nameEn: "Guinean Franc", nameFa: "فرانک گینه" },
{ code: "GTQ", nameEn: "Guatemalan Quetzal", nameFa: "کوتزال گواتمالا" },
{ code: "GYD", nameEn: "Guyanese Dollar", nameFa: "دلار گویان" },
{ code: "HKD", nameEn: "Hong Kong Dollar", nameFa: "دلار هنگ کنگ" },
{ code: "HNL", nameEn: "Honduran Lempira", nameFa: "لمپیرای هندوراس" },
{ code: "HRK", nameEn: "Croatian Kuna", nameFa: "کونای کرواسی" },
{ code: "HTG", nameEn: "Haitian Gourde", nameFa: "گورد هائیتی" },
{ code: "HUF", nameEn: "Hungarian Forint", nameFa: "فورینت مجارستان" },
{ code: "IDR", nameEn: "Indonesian Rupiah", nameFa: "روپیه اندونزی" },
{ code: "ILS", nameEn: "Israeli Shekel", nameFa: "شکل اسرائیل" },
{ code: "INR", nameEn: "Indian Rupee", nameFa: "روپیه هند" },
{ code: "IQD", nameEn: "Iraqi Dinar", nameFa: "دینار عراق" },
{ code: "IRR", nameEn: "Iranian Rial", nameFa: "ریال ایران" },
{ code: "ISK", nameEn: "Icelandic Króna", nameFa: "کرون ایسلند" },
{ code: "JMD", nameEn: "Jamaican Dollar", nameFa: "دلار جامائیکا" },
{ code: "JOD", nameEn: "Jordanian Dinar", nameFa: "دینار اردن" },
{ code: "JPY", nameEn: "Japanese Yen", nameFa: "ین ژاپن" },
{ code: "KES", nameEn: "Kenyan Shilling", nameFa: "شیلینگ کنیا" },
{ code: "KGS", nameEn: "Kyrgystani Som", nameFa: "سوم قرقیزستان" },
{ code: "KHR", nameEn: "Cambodian Riel", nameFa: "ریال کامبوج" },
{ code: "KMF", nameEn: "Comorian Franc", nameFa: "فرانک کومور" },
{ code: "KPW", nameEn: "North Korean Won", nameFa: "وون کره شمالی" },
{ code: "KRW", nameEn: "South Korean Won", nameFa: "وون کره جنوبی" },
{ code: "KWD", nameEn: "Kuwaiti Dinar", nameFa: "دینار کویت" },
{ code: "KYD", nameEn: "Cayman Islands Dollar", nameFa: "دلار جزایر کیمن" },
{ code: "KZT", nameEn: "Kazakhstani Tenge", nameFa: "تنگه قزاقستان" },
{ code: "LAK", nameEn: "Laotian Kip", nameFa: "کیپ لائوس" },
{ code: "LBP", nameEn: "Lebanese Pound", nameFa: "پوند لبنان" },
{ code: "LKR", nameEn: "Sri Lankan Rupee", nameFa: "روپیه سریلانکا" },
{ code: "LRD", nameEn: "Liberian Dollar", nameFa: "دلار لیبریا" },
{ code: "LSL", nameEn: "Lesotho Loti", nameFa: "لوتی لسوتو" },
{ code: "LYD", nameEn: "Libyan Dinar", nameFa: "دینار لیبی" },
{ code: "MAD", nameEn: "Moroccan Dirham", nameFa: "درهم مراکش" },
{ code: "MDL", nameEn: "Moldovan Leu", nameFa: "لوی مولداوی" },
{ code: "MGA", nameEn: "Malagasy Ariary", nameFa: "آریاری ماداگاسکار" },
{ code: "MKD", nameEn: "Macedonian Denar", nameFa: "دینار مقدونیه" },
{ code: "MMK", nameEn: "Myanmar Kyat", nameFa: "کیات میانمار" },
{ code: "MNT", nameEn: "Mongolian Tugrik", nameFa: "توگریک مغولستان" },
{ code: "MOP", nameEn: "Macanese Pataca", nameFa: "پاتاکای ماکائو" },
{ code: "MRU", nameEn: "Mauritanian Ouguiya", nameFa: "اوگیای موریتانی" },
{ code: "MUR", nameEn: "Mauritian Rupee", nameFa: "روپیه موریس" },
{ code: "MVR", nameEn: "Maldivian Rufiyaa", nameFa: "روفیای مالدیو" },
{ code: "MWK", nameEn: "Malawian Kwacha", nameFa: "کواچای مالاوی" },
{ code: "MXN", nameEn: "Mexican Peso", nameFa: "پزو مکزیک" },
{ code: "MYR", nameEn: "Malaysian Ringgit", nameFa: "رینگیت مالزی" },
{ code: "MZN", nameEn: "Mozambican Metical", nameFa: "متیکال موزامبیک" },
{ code: "NAD", nameEn: "Namibian Dollar", nameFa: "دلار نامیبیا" },
{ code: "NGN", nameEn: "Nigerian Naira", nameFa: "نایرای نیجریه" },
{ code: "NIO", nameEn: "Nicaraguan Córdoba", nameFa: "کوردوبای نیکاراگوئه" },
{ code: "NOK", nameEn: "Norwegian Krone", nameFa: "کرون نروژ" },
{ code: "NPR", nameEn: "Nepalese Rupee", nameFa: "روپیه نپال" },
{ code: "NZD", nameEn: "New Zealand Dollar", nameFa: "دلار نیوزیلند" },
{ code: "OMR", nameEn: "Omani Rial", nameFa: "ریال عمان" },
{ code: "PAB", nameEn: "Panamanian Balboa", nameFa: "بالبوای پاناما" },
{ code: "PEN", nameEn: "Peruvian Sol", nameFa: "سول پرو" },
{
code: "PGK",
nameEn: "Papua New Guinean Kina",
nameFa: "کینای پاپوآ گینه نو",
},
{ code: "PHP", nameEn: "Philippine Peso", nameFa: "پزو فیلیپین" },
{ code: "PKR", nameEn: "Pakistani Rupee", nameFa: "روپیه پاکستان" },
{ code: "PLN", nameEn: "Polish Zloty", nameFa: "زلوتی لهستان" },
{ code: "PYG", nameEn: "Paraguayan Guarani", nameFa: "گوارانی پاراگوئه" },
{ code: "QAR", nameEn: "Qatari Rial", nameFa: "ریال قطر" },
{ code: "RON", nameEn: "Romanian Leu", nameFa: "لوی رومانی" },
{ code: "RSD", nameEn: "Serbian Dinar", nameFa: "دینار صربستان" },
{ code: "RUB", nameEn: "Russian Ruble", nameFa: "روبل روسیه" },
{ code: "RWF", nameEn: "Rwandan Franc", nameFa: "فرانک رواندا" },
{ code: "SAR", nameEn: "Saudi Riyal", nameFa: "ریال عربستان" },
{
code: "SBD",
nameEn: "Solomon Islands Dollar",
nameFa: "دلار جزایر سلیمان",
},
{ code: "SCR", nameEn: "Seychellois Rupee", nameFa: "روپیه سیشل" },
{ code: "SDG", nameEn: "Sudanese Pound", nameFa: "پوند سودان" },
{ code: "SEK", nameEn: "Swedish Krona", nameFa: "کرون سوئد" },
{ code: "SGD", nameEn: "Singapore Dollar", nameFa: "دلار سنگاپور" },
{ code: "SHP", nameEn: "St. Helena Pound", nameFa: "پوند سنت هلن" },
{ code: "SLL", nameEn: "Sierra Leonean Leone", nameFa: "لئون سیرالئون" },
{ code: "SOS", nameEn: "Somali Shilling", nameFa: "شیلینگ سومالی" },
{ code: "SRD", nameEn: "Surinamese Dollar", nameFa: "دلار سورینام" },
{ code: "SSP", nameEn: "South Sudanese Pound", nameFa: "پوند سودان جنوبی" },
{ code: "STN", nameEn: "São Tomé Dobra", nameFa: "دوبرا سائوتومه" },
{ code: "SVC", nameEn: "Salvadoran Colón", nameFa: "کولون السالوادور" },
{ code: "SYP", nameEn: "Syrian Pound", nameFa: "پوند سوریه" },
{ code: "SZL", nameEn: "Swazi Lilangeni", nameFa: "لیلانگنی سوازیلند" },
{ code: "THB", nameEn: "Thai Baht", nameFa: "بات تایلند" },
{ code: "TJS", nameEn: "Tajikistani Somoni", nameFa: "سامانی تاجیکستان" },
{ code: "TMT", nameEn: "Turkmenistani Manat", nameFa: "منات ترکمنستان" },
{ code: "TND", nameEn: "Tunisian Dinar", nameFa: "دینار تونس" },
{ code: "TOMAN", nameEn: "Iranian Toman", nameFa: "تومان ایران" },
{ code: "TOP", nameEn: "Tongan Paʻanga", nameFa: "پاآنگای تونگا" },
{ code: "TRY", nameEn: "Turkish Lira", nameFa: "لیر ترکیه" },
{
code: "TTD",
nameEn: "Trinidad & Tobago Dollar",
nameFa: "دلار ترینیداد و توباگر",
},
{ code: "TWD", nameEn: "New Taiwan Dollar", nameFa: "دلار جدید تایوان" },
{ code: "TZS", nameEn: "Tanzanian Shilling", nameFa: "شیلینگ تانزانیا" },
{ code: "UAH", nameEn: "Ukrainian Hryvnia", nameFa: "گریونا اوکراین" },
{ code: "UGX", nameEn: "Ugandan Shilling", nameFa: "شیلینگ اوگاندا" },
{ code: "USD", nameEn: "US Dollar", nameFa: "دلار آمریکا" },
{ code: "UYU", nameEn: "Uruguayan Peso", nameFa: "پزو اروگوئه" },
{ code: "UZS", nameEn: "Uzbekistani Som", nameFa: "سوم ازبکستان" },
{ code: "VES", nameEn: "Venezuelan Bolívar", nameFa: "بولیوار ونزوئلا" },
{ code: "VND", nameEn: "Vietnamese Dong", nameFa: "دانگ ویتنام" },
{ code: "VUV", nameEn: "Vanuatu Vatu", nameFa: "واتو وانواتو" },
{ code: "WST", nameEn: "Samoan Tālā", nameFa: "تالای ساموآ" },
{
code: "XAF",
nameEn: "Central African CFA Franc",
nameFa: "فرانک سی‌اف‌آی آفریقای مرکزی",
},
{ code: "XCD", nameEn: "East Caribbean Dollar", nameFa: "دلار کارائیب شرقی" },
{
code: "XOF",
nameEn: "West African CFA Franc",
nameFa: "فرانک سی‌اف‌آی آفریقای غربی",
},
{ code: "XPF", nameEn: "CFP Franc", nameFa: "فرانک اقیانوس آرام" },
{ code: "YER", nameEn: "Yemeni Rial", nameFa: "ریال یمن" },
{ code: "ZAR", nameEn: "South African Rand", nameFa: "راند آفریقای جنوبی" },
{ code: "ZMW", nameEn: "Zambian Kwacha", nameFa: "کواچای زامبیا" },
{ code: "ZWL", nameEn: "Zimbabwean Dollar", nameFa: "دلار زیمبابوه" },
];
function formatNumberWithCommas(
val: string | number | null | undefined,
): string {
if (val === null || val === undefined || val === "") return "";
const cleanStr = String(val).replace(/,/g, "");
const parts = cleanStr.split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join(".");
}