Expand sales, treasury, and parties modules with Jalali calendar and FunZone integrations

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Shayan Azadi
2026-07-03 03:15:52 +03:30
parent d7252f1d21
commit 4508be582a
55 changed files with 5083 additions and 799 deletions

View File

@@ -8,6 +8,7 @@ import {
DataTable,
Field,
Input,
JalaliDateInput,
Modal,
PageHeader,
Select,
@@ -15,7 +16,7 @@ import {
} from '../components/ui'
import type { Voucher, VoucherLine } from '../types'
import { createId, nextNumber } from '../utils/id'
import { formatDate, formatMoney, toFa } from '../utils/format'
import { formatDate, formatMoney, todayISO, toFa } from '../utils/format'
import { voucherBalance } from '../utils/accounting'
const newLine = (): VoucherLine => ({
@@ -30,7 +31,7 @@ function createDraft(items: Voucher[]): Voucher {
return {
id: '',
number: nextNumber(items),
date: new Date().toISOString().slice(0, 10),
date: todayISO(),
description: '',
status: 'draft',
lines: [newLine(), newLine()],
@@ -74,18 +75,45 @@ export function Vouchers() {
}
const columns: Array<Column<Voucher>> = [
{ key: 'number', header: 'شماره', render: (v) => <span className="font-mono">{toFa(v.number)}</span> },
{ key: 'date', header: 'تاریخ', render: (v) => formatDate(v.date) },
{ key: 'desc', header: 'شرح', render: (v) => <span className="text-slate-700">{v.description || '—'}</span> },
{
key: 'number',
header: 'شماره',
headerClassName: 'w-[4.5rem]',
className: 'w-[4.5rem] whitespace-nowrap',
render: (v) => <span className="font-mono">{toFa(v.number)}</span>,
},
{
key: 'date',
header: 'تاریخ',
headerClassName: 'w-[6.5rem]',
className: 'w-[6.5rem] whitespace-nowrap tabular-nums',
render: (v) => formatDate(v.date),
},
{
key: 'desc',
header: 'شرح',
truncate: true,
className: 'min-w-0',
render: (v) => (
<span className="block truncate text-slate-700" title={v.description || undefined}>
{v.description || '—'}
</span>
),
},
{
key: 'amount',
header: 'مبلغ',
align: 'end',
render: (v) => <span className="tabular-nums">{formatMoney(voucherBalance(v).debit)}</span>,
headerClassName: 'w-[7.5rem]',
className: 'w-[7.5rem] whitespace-nowrap tabular-nums',
render: (v) => formatMoney(voucherBalance(v).debit),
},
{
key: 'status',
header: 'وضعیت',
sticky: { left: '9rem' },
headerClassName: 'w-[6.5rem]',
className: 'w-[6.5rem] whitespace-nowrap',
render: (v) =>
v.status === 'posted' ? <Badge tone="green">ثبت قطعی</Badge> : <Badge tone="amber">پیشنویس</Badge>,
},
@@ -93,6 +121,9 @@ export function Vouchers() {
key: 'actions',
header: '',
align: 'end',
sticky: { left: '0' },
headerClassName: 'w-[9rem]',
className: 'w-[9rem] whitespace-nowrap',
render: (v) => (
<div className="flex justify-end gap-1">
<Button size="sm" variant="ghost" onClick={() => setDraft({ ...v, lines: v.lines.map((l) => ({ ...l })) })}>
@@ -171,7 +202,7 @@ export function Vouchers() {
/>
</Field>
<Field label="تاریخ">
<Input type="date" value={draft.date} onChange={(e) => setDraft({ ...draft, date: e.target.value })} />
<JalaliDateInput value={draft.date} onChange={(date) => setDraft({ ...draft, date })} />
</Field>
<Field label="شرح سند">
<Input value={draft.description} onChange={(e) => setDraft({ ...draft, description: e.target.value })} />