Files
kakeibo/frontend/src/lib/format.ts
T
suphicakir 16535ad7f3 feat(defter): haftanın girişleri, düzenle/sil + geri al, haftalık özet
- Girişler gün gün, gün toplamıyla; satırda kategori, grup, ödeme türü, açıklama
- Düzenleme aynı formda (sürüm kontrolü, pasif kategori korunur)
- Silmede "Geri al" bildirimi; grup bazlı bütçe / harcanan / kalan özeti

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 11:29:33 +03:00

132 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Tüm sayı / para / tarih biçimlendirmesi buradan yapılır (KAKEIBO_PROMPT §9).
*
* - Para: backend'den **kuruş cinsinden tamsayı** gelir → `1.250,00 ₺`
* (para birimi rakamın sağında, arada bir boşluk)
* - Miktar (altın, döviz): milyonda bir birim (`_e6`) → `12,50 gr`
* - Tarih: `GG.AA.YYYY`
*/
export type CurrencyCode = 'TRY' | 'USD' | 'EUR' | 'XAU_GRAM'
const CURRENCY_SUFFIX: Record<CurrencyCode, string> = {
TRY: '₺',
USD: '$',
EUR: '€',
XAU_GRAM: 'gr',
}
const numberFormats = new Map<number, Intl.NumberFormat>()
function numberFormat(decimals: number): Intl.NumberFormat {
let f = numberFormats.get(decimals)
if (!f) {
f = new Intl.NumberFormat('tr-TR', {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals,
})
numberFormats.set(decimals, f)
}
return f
}
/** Ondalıklı sayıyı Türkçe biçimler: `1250.5` → `1.250,50` */
export function formatNumber(value: number, decimals = 2): string {
return numberFormat(decimals).format(value)
}
/** Kuruş → `1.250,00 ₺`. `signed: true` ise pozitiflere `+` eklenir. */
export function formatMoney(
kurus: number,
currency: CurrencyCode = 'TRY',
options: { signed?: boolean } = {},
): string {
const text = `${formatNumber(kurus / 100)} ${CURRENCY_SUFFIX[currency]}`
return options.signed && kurus > 0 ? `+${text}` : text
}
/** Milyonda bir birim → `12,50 gr` (altın, döviz miktarı) */
export function formatQuantity(e6: number, unit: string, decimals = 2): string {
return `${formatNumber(e6 / 1_000_000, decimals)} ${unit}`
}
/** Yüzde: `0.123` → `%12,3` (Türkçede % işareti başta) */
export function formatPercent(ratio: number, decimals = 1): string {
return `%${formatNumber(ratio * 100, decimals)}`
}
/**
* Kullanıcının yazdığı tutarı kuruşa çevirir. Geçersizse `null`.
*
* Kabul edilenler: `1250` · `1.250` · `1250,5` · `1.250,50` · `1250.50` (telefon klavyesi)
* Kural: virgül varsa ondalık ayırıcıdır, noktalar binliktir.
* Virgül yoksa ve son noktadan sonra 1–2 rakam varsa o nokta ondalıktır.
* Float hatası olmasın diye hesap metin üzerinden yapılır.
*/
export function parseMoney(input: string): number | null {
let s = input.replace(/[\s₺]/g, '')
if (!s) return null
let negative = false
if (s.startsWith('-')) {
negative = true
s = s.slice(1)
}
let intPart: string
let fracPart = ''
if (s.includes(',')) {
const parts = s.split(',')
if (parts.length !== 2) return null
intPart = parts[0].replace(/\./g, '')
fracPart = parts[1]
} else {
const m = /^(.*)\.(\d{1,2})$/.exec(s)
if (m && !m[1].includes('.')) {
intPart = m[1]
fracPart = m[2]
} else {
intPart = s.replace(/\./g, '')
}
}
if (intPart === '') intPart = '0'
if (!/^\d+$/.test(intPart) || !/^\d{0,2}$/.test(fracPart)) return null
const kurus = Number(intPart) * 100 + Number(fracPart.padEnd(2, '0'))
if (!Number.isSafeInteger(kurus)) return null
return negative ? -kurus : kurus
}
/** `"2026-12-09"` veya ISO zaman damgası → `09.12.2026` */
export function formatDate(value: string | Date): string {
if (typeof value === 'string') {
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value)
if (m) return `${m[3]}.${m[2]}.${m[1]}`
value = new Date(value)
}
const dd = String(value.getDate()).padStart(2, '0')
const mm = String(value.getMonth() + 1).padStart(2, '0')
return `${dd}.${mm}.${value.getFullYear()}`
}
/** Yerel tarih → `"2026-12-09"` (backend'in takvim tarihi biçimi) */
export function toIsoDate(date: Date): string {
const mm = String(date.getMonth() + 1).padStart(2, '0')
const dd = String(date.getDate()).padStart(2, '0')
return `${date.getFullYear()}-${mm}-${dd}`
}
/** `"2026-12-07"` → `07.12` (hafta aralıkları gibi kısa gösterimler) */
export function formatDayMonth(iso: string): string {
const m = /^\d{4}-(\d{2})-(\d{2})$/.exec(iso)
return m ? `${m[2]}.${m[1]}` : iso
}
const weekdayFormat = new Intl.DateTimeFormat('tr-TR', { weekday: 'long', timeZone: 'UTC' })
/** `"2026-11-12"` → `Perşembe` (UTC üzerinden: saat dilimi günü kaydırmaz) */
export function formatWeekday(iso: string): string {
const [y, m, d] = iso.split('-').map(Number)
return weekdayFormat.format(new Date(Date.UTC(y, m - 1, d)))
}