Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/web/src/components/settings/AuditSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ export function AuditSettings({ logs }) {

return (
<section className="overflow-hidden rounded-xl border bg-card">
<div className="border-b p-5">
<div className="border-b bg-muted/25 px-4 py-4 sm:px-5">
<h2 className="text-lg font-semibold">Audit</h2>
<p className="mt-1 text-sm text-muted-foreground">Telusuri perubahan sistem dan buka detail sebelum/sesudah ketika tersedia.</p>
</div>
<div className="grid gap-3 border-b p-4 md:grid-cols-4">
<div className="grid gap-3 border-b bg-card p-4 sm:grid-cols-2 xl:grid-cols-4">
<Input placeholder="Cari pengguna" value={userFilter} onChange={(e) => setUserFilter(e.target.value)} />
<Select value={entityFilter} onValueChange={setEntityFilter}>
<SelectTrigger className="h-10 w-full"><SelectValue /></SelectTrigger>
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/settings/CategorySettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ export function CategorySettings({ categories, api, reload }) {

return (
<section className="overflow-hidden rounded-xl border bg-card">
<div className="flex items-center justify-between gap-4 border-b p-5">
<div className="flex flex-col gap-3 border-b bg-muted/25 px-4 py-4 sm:flex-row sm:items-center sm:justify-between sm:px-5">
<div><h2 className="text-lg font-semibold">Kategori</h2><p className="mt-1 text-sm text-muted-foreground">Kelola kategori kas masuk dan kas keluar.</p></div>
<Button onClick={() => setOpen(true)}><IconPlus />Tambah kategori</Button>
<Button className="w-full sm:w-auto" onClick={() => setOpen(true)}><IconPlus />Tambah kategori</Button>
</div>
<Table>
<TableHeader><TableRow><TableHead>Nama</TableHead><TableHead>Jenis</TableHead><TableHead>Urutan</TableHead><TableHead>Status</TableHead><TableHead className="w-16" /></TableRow></TableHeader>
Expand Down
24 changes: 13 additions & 11 deletions apps/web/src/components/settings/FinanceSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,25 @@ import { formatRupiah } from '@/lib/format';

export function FinanceSettings({ settings, setSettings, onSave, saving }) {
return (
<section className="rounded-xl border bg-card p-5">
<div className="mb-6">
<section className="overflow-hidden rounded-xl border bg-card">
<div className="border-b bg-muted/25 px-5 py-4">
<h2 className="text-lg font-semibold">Keuangan</h2>
<p className="mt-1 text-sm text-muted-foreground">Atur saldo awal yang menjadi dasar perhitungan kas.</p>
</div>
<Alert className="mb-6">
<div className="p-5 pb-0"><Alert>
<AlertTitle>Perubahan memengaruhi saldo sistem</AlertTitle>
<AlertDescription>Setiap perubahan saldo awal dan tanggalnya dicatat pada audit trail.</AlertDescription>
</Alert>
<form className="space-y-5" onSubmit={onSave}>
</Alert></div>
<form className="space-y-5 p-5" onSubmit={onSave}>
<FieldGroup>
<Field>
<FieldLabel>Saldo awal</FieldLabel>
<Input type="number" min="0" step="1" value={settings.openingBalance} onChange={(e) => setSettings((x) => ({ ...x, openingBalance: e.target.value }))} required />
<span className="text-xs text-muted-foreground">{formatRupiah(Number(settings.openingBalance || 0))}</span>
</Field>
<Field><FieldLabel>Tanggal saldo awal</FieldLabel><Input type="date" value={settings.openingBalanceDate ?? ''} onChange={(e) => setSettings((x) => ({ ...x, openingBalanceDate: e.target.value }))} /></Field>
<div className="grid gap-4 md:grid-cols-2">
<Field>
<FieldLabel>Saldo awal</FieldLabel>
<Input type="number" min="0" step="1" value={settings.openingBalance} onChange={(e) => setSettings((x) => ({ ...x, openingBalance: e.target.value }))} required />
<span className="text-xs text-muted-foreground">{formatRupiah(Number(settings.openingBalance || 0))}</span>
</Field>
<Field><FieldLabel>Tanggal saldo awal</FieldLabel><Input type="date" value={settings.openingBalanceDate ?? ''} onChange={(e) => setSettings((x) => ({ ...x, openingBalanceDate: e.target.value }))} /></Field>
</div>
<Field><FieldLabel>Catatan</FieldLabel><Textarea rows={4} maxLength={240} value={settings.openingBalanceNote ?? ''} onChange={(e) => setSettings((x) => ({ ...x, openingBalanceNote: e.target.value }))} placeholder="Contoh: Saldo kas sebelum migrasi ke IKHLAS" /></Field>
</FieldGroup>
<div className="flex justify-end border-t pt-5"><Button disabled={saving}>{saving ? 'Menyimpan...' : 'Simpan keuangan'}</Button></div>
Expand Down
12 changes: 7 additions & 5 deletions apps/web/src/components/settings/GeneralSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import { Input } from '@/components/ui/input';

export function GeneralSettings({ settings, setSettings, onSave, saving }) {
return (
<section className="rounded-xl border bg-card p-5">
<div className="mb-6">
<section className="overflow-hidden rounded-xl border bg-card">
<div className="border-b bg-muted/25 px-5 py-4">
<h2 className="text-lg font-semibold">Umum</h2>
<p className="mt-1 text-sm text-muted-foreground">Identitas masjid, rekening donasi, dan media live.</p>
</div>
<form className="space-y-7" onSubmit={onSave}>
<form className="space-y-7 p-5" onSubmit={onSave}>
<div>
<h3 className="mb-4 text-sm font-semibold">Identitas</h3>
<FieldGroup>
<Field><FieldLabel>Nama masjid</FieldLabel><Input value={settings.mosqueName} onChange={(e) => setSettings((x) => ({ ...x, mosqueName: e.target.value }))} required /></Field>
<Field><FieldLabel>Tagline</FieldLabel><Input value={settings.mosqueTagline} onChange={(e) => setSettings((x) => ({ ...x, mosqueTagline: e.target.value }))} /></Field>
<div className="grid gap-4 md:grid-cols-2">
<Field><FieldLabel>Nama masjid</FieldLabel><Input value={settings.mosqueName} onChange={(e) => setSettings((x) => ({ ...x, mosqueName: e.target.value }))} required /></Field>
<Field><FieldLabel>Tagline</FieldLabel><Input value={settings.mosqueTagline} onChange={(e) => setSettings((x) => ({ ...x, mosqueTagline: e.target.value }))} /></Field>
</div>
</FieldGroup>
</div>

Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/settings/PublicDisplaySettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ export function PublicDisplaySettings({ messages, api, reload }) {

return (
<section className="overflow-hidden rounded-xl border bg-card">
<div className="flex items-center justify-between gap-4 border-b p-5">
<div className="flex flex-col gap-3 border-b bg-muted/25 px-4 py-4 sm:flex-row sm:items-center sm:justify-between sm:px-5">
<div><h2 className="text-lg font-semibold">Public Display</h2><p className="mt-1 text-sm text-muted-foreground">Kelola running text, pengumuman, dan pesan yang tampil ke jamaah.</p></div>
<Button onClick={add}><IconPlus />Tambah konten</Button>
<Button className="w-full sm:w-auto" onClick={add}><IconPlus />Tambah konten</Button>
</div>
<Table>
<TableHeader><TableRow><TableHead>Judul</TableHead><TableHead>Isi singkat</TableHead><TableHead>Sumber</TableHead><TableHead>Urutan</TableHead><TableHead>Status</TableHead><TableHead className="w-16" /></TableRow></TableHeader>
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/components/settings/SettingsNav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@ const items = [
export function SettingsNav({ value, onChange }) {
return (
<>
<div className="md:hidden">
<div className="lg:hidden">
<Select value={value} onValueChange={onChange}>
<SelectTrigger className="h-10 w-full"><SelectValue /></SelectTrigger>
<SelectContent>
{items.map(([id, label]) => <SelectItem key={id} value={id}>{label}</SelectItem>)}
</SelectContent>
</Select>
</div>
<nav className="hidden rounded-xl border bg-card p-2 md:block" aria-label="Bagian pengaturan">
<nav className="hidden lg:sticky lg:top-20 lg:block" aria-label="Bagian pengaturan">
{items.map(([id, label]) => (
<button
key={id}
type="button"
onClick={() => onChange(id)}
className={`flex w-full items-center rounded-lg px-3 py-2.5 text-left text-sm font-medium transition-colors ${value === id ? 'bg-accent text-accent-foreground shadow-[inset_3px_0_0_var(--ui-primary)]' : 'text-muted-foreground hover:bg-muted/80 hover:text-foreground'}`}
className={`flex w-full items-center rounded-md px-3 py-2.5 text-left text-sm font-medium transition-colors ${value === id ? 'bg-accent text-accent-foreground shadow-[inset_3px_0_0_var(--ui-primary)]' : 'text-muted-foreground hover:bg-muted/80 hover:text-foreground'}`}
>
{label}
</button>
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/settings/UserSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ export function UserSettings({ users, currentUser, api, reload }) {

return (
<section className="overflow-hidden rounded-xl border bg-card">
<div className="flex items-center justify-between gap-4 border-b p-5">
<div className="flex flex-col gap-3 border-b bg-muted/25 px-4 py-4 sm:flex-row sm:items-center sm:justify-between sm:px-5">
<div><h2 className="text-lg font-semibold">Pengguna</h2><p className="mt-1 text-sm text-muted-foreground">Kelola akun Admin dan Bendahara serta status aksesnya.</p></div>
<Button onClick={() => setOpen(true)}><IconPlus />Tambah pengguna</Button>
<Button className="w-full sm:w-auto" onClick={() => setOpen(true)}><IconPlus />Tambah pengguna</Button>
</div>
<Table>
<TableHeader><TableRow><TableHead>Nama</TableHead><TableHead>Email</TableHead><TableHead>Role</TableHead><TableHead>Status</TableHead><TableHead className="w-24">Akses</TableHead></TableRow></TableHeader>
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/pages/AdminSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default function AdminSettings() {
if (!settings && !loadingError) return <LoadingState label="Memuat pengaturan..." />;

return (
<div className="space-y-7">
<div className="space-y-5">
<PageHeader
title="Pengaturan"
description="Kelola identitas masjid, keuangan awal, kategori, Public Display, pengguna, dan audit tanpa menumpuk semuanya dalam satu halaman."
Expand All @@ -80,9 +80,9 @@ export default function AdminSettings() {
)}

{settings && (
<div className="grid gap-5 md:grid-cols-[220px_minmax(0,1fr)] md:items-start">
<div className="grid gap-5 lg:grid-cols-[190px_minmax(0,1fr)] lg:items-start">
<SettingsNav value={active} onChange={setActive} />
<div className="min-w-0">
<div className="min-w-0 max-w-5xl">
{active === 'general' && <GeneralSettings settings={settings} setSettings={setSettings} onSave={saveSettings} saving={savingSettings} />}
{active === 'finance' && <FinanceSettings settings={settings} setSettings={setSettings} onSave={saveSettings} saving={savingSettings} />}
{active === 'categories' && <CategorySettings categories={categories} api={api} reload={load} />}
Expand Down
12 changes: 6 additions & 6 deletions apps/web/src/pages/Dashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ function formatAxisAmount(value) {
return new Intl.NumberFormat('id-ID', { maximumFractionDigits: 0 }).format(number);
}

function Metric({ label, value, tone = 'default', caption }) {
function Metric({ label, value, tone = 'default', caption, featured = false }) {
const valueClass = tone === 'income'
? 'text-primary'
: tone === 'expense'
? 'text-destructive'
: 'text-foreground';

return (
<div className="min-w-0 px-4 py-4 sm:px-5">
<div className={`min-w-0 px-4 py-4 sm:px-5 ${featured ? 'bg-secondary/65' : ''}`}>
<div className="text-xs font-medium text-muted-foreground">{label}</div>
<strong className={`mt-1.5 block truncate text-[clamp(1.25rem,2.5vw,1.8rem)] font-semibold tracking-[-0.035em] ${valueClass}`}>
{value}
Expand Down Expand Up @@ -133,17 +133,17 @@ export default function Dashboard() {
) : (
<>
<Card className="overflow-hidden">
<div className="grid divide-y sm:grid-cols-2 sm:divide-x sm:divide-y-0 xl:grid-cols-4">
<Metric label="Saldo saat ini" value={formatRupiah(data.summary.currentBalance)} caption="Saldo berjalan seluruh periode" />
<div className="grid divide-y sm:grid-cols-2 sm:divide-x sm:divide-y-0 xl:grid-cols-[1.15fr_repeat(3,1fr)]">
<Metric label="Saldo saat ini" value={formatRupiah(data.summary.currentBalance)} caption="Saldo berjalan seluruh periode" featured />
<Metric label="Kas masuk bulan ini" value={`+${formatRupiah(data.monthSummary.totalIncome)}`} tone="income" caption={`${data.monthSummary.transactionCount} transaksi periode berjalan`} />
<Metric label="Kas keluar bulan ini" value={`-${formatRupiah(data.monthSummary.totalExpense)}`} tone="expense" caption={`Per ${formatDate(data.monthSummary.to)}`} />
<Metric label="Saldo awal" value={formatRupiah(data.summary.openingBalance)} caption="Basis perhitungan kas" />
</div>
</Card>

<div className="grid gap-4 xl:grid-cols-[minmax(0,1fr)_330px]">
<div className="grid gap-4 xl:grid-cols-[minmax(0,1fr)_360px]">
<Card className="min-w-0">
<CardHeader className="flex-row items-start justify-between gap-3">
<CardHeader className="flex-row items-start justify-between gap-3 border-b bg-muted/25">
<div>
<CardTitle>Arus kas 30 hari terakhir</CardTitle>
<CardDescription>Pergerakan kas masuk dan kas keluar harian.</CardDescription>
Expand Down
14 changes: 7 additions & 7 deletions apps/web/src/pages/Reports.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export default function Reports() {

<Card>
<CardContent className="pt-5">
<div className="flex flex-col gap-3 lg:flex-row lg:items-end">
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto] xl:items-end">
<label className="flex min-w-0 flex-1 flex-col gap-1.5 text-xs font-medium text-muted-foreground">
Dari tanggal
<Input type="date" value={range.from} onChange={(event) => setRange((current) => ({ ...current, from: event.target.value }))} />
Expand All @@ -142,7 +142,7 @@ export default function Reports() {
Sampai tanggal
<Input type="date" value={range.to} onChange={(event) => setRange((current) => ({ ...current, to: event.target.value }))} />
</label>
<div className="flex flex-wrap gap-2">
<div className="flex flex-wrap items-center gap-1 rounded-lg bg-muted/55 p-1 md:col-span-2 xl:col-span-1">
<Button variant="ghost" size="sm" onClick={() => applyPreset('month')}>Bulan ini</Button>
<Button variant="ghost" size="sm" onClick={() => applyPreset('30d')}>30 hari</Button>
<Button variant="ghost" size="sm" onClick={() => applyPreset('90d')}>90 hari</Button>
Expand All @@ -163,7 +163,7 @@ export default function Reports() {
{data && (
<>
<Card className="overflow-hidden">
<div className="grid divide-y sm:grid-cols-2 sm:divide-x sm:divide-y-0 xl:grid-cols-4">
<div className="grid divide-y sm:grid-cols-2 sm:divide-x sm:divide-y-0 xl:grid-cols-[1fr_1fr_1fr_1.15fr]">
<Metric icon={IconWallet} label="Saldo awal periode" value={formatRupiah(data.summary.openingBalance)} caption={formatDate(data.summary.from)} />
<Metric icon={IconTrendingUp} label="Kas masuk" value={`+${formatRupiah(data.summary.totalIncome)}`} tone="income" caption={`${data.summary.transactionCount} transaksi total`} />
<Metric icon={IconTrendingDown} label="Kas keluar" value={`-${formatRupiah(data.summary.totalExpense)}`} tone="expense" caption={`Net ${formatRupiah(data.summary.netChange)}`} />
Expand All @@ -173,12 +173,12 @@ export default function Reports() {

<div className="grid gap-4 xl:grid-cols-2">
<Card className="min-w-0">
<CardHeader>
<CardHeader className="border-b bg-muted/25">
<CardTitle>Arus kas periode</CardTitle>
<CardDescription>Kas masuk dan keluar per hari pada rentang yang dipilih.</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer config={cashflowConfig} className="h-[310px] min-h-[280px]">
<ChartContainer config={cashflowConfig} className="h-[290px] min-h-[270px]">
<BarChart data={data.cashflow} margin={{ left: 0, right: 8, top: 8, bottom: 0 }}>
<CartesianGrid vertical={false} strokeDasharray="3 3" stroke="var(--ui-border)" />
<XAxis
Expand All @@ -204,13 +204,13 @@ export default function Reports() {
</Card>

<Card className="min-w-0">
<CardHeader>
<CardHeader className="border-b bg-muted/25">
<CardTitle>Distribusi kategori</CardTitle>
<CardDescription>Delapan kategori dengan nominal terbesar pada periode ini.</CardDescription>
</CardHeader>
<CardContent>
{categoryData.length ? (
<ChartContainer config={categoryConfig} className="h-[310px] min-h-[280px]">
<ChartContainer config={categoryConfig} className="h-[290px] min-h-[270px]">
<BarChart data={categoryData} layout="vertical" margin={{ left: 8, right: 24, top: 8, bottom: 0 }}>
<CartesianGrid horizontal={false} strokeDasharray="3 3" stroke="var(--ui-border)" />
<XAxis type="number" axisLine={false} tickLine={false} tickMargin={8} tick={{ fontSize: 11 }} tickFormatter={formatAxisAmount} />
Expand Down
Loading
Loading