Files
afriadirahman 8c6dad2a31
tests / ci (push) Canceled after 0s
Admin Panel
2026-09-29 14:57:37 +07:00

268 lines
12 KiB
TypeScript

import { Form, Head } from '@inertiajs/react';
import { ExternalLink, MapPin, Pencil, Plus, Trash2 } from 'lucide-react';
import type { ReactNode } from 'react';
import {
ConfirmDelete,
FormDialog,
FormField,
PageHeader,
} from '@/components/admin/form-controls';
import Heading from '@/components/heading';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { officeAddress } from '@/data/site';
import { edit } from '@/routes/admin/company';
import { update as updateContact } from '@/routes/admin/company/contact';
import { destroy, store, update } from '@/routes/admin/offices';
import type { Office, SiteInfo } from '@/types';
export default function CompanyEdit({ contact, offices }: SiteInfo) {
return (
<>
<Head title="Info Perusahaan" />
<div className="flex flex-col gap-8 p-4 md:p-6">
<PageHeader
title="Info Perusahaan"
description="Kontak dan alamat kantor yang tampil di header, footer, dan halaman Kontak."
/>
<section className="bg-card flex flex-col gap-6 rounded-xl border p-5">
<Heading
variant="small"
title="Kontak"
description="Email busdev juga menjadi tujuan notifikasi konsultasi baru."
/>
<Form
{...updateContact.form()}
options={{ preserveScroll: true }}
className="grid gap-4 sm:grid-cols-2"
>
{({ processing, errors }) => (
<>
<FormField
id="phone"
label="Telepon kantor"
error={errors.phone}
>
<Input
id="phone"
name="phone"
defaultValue={contact.phone}
required
/>
</FormField>
<FormField
id="whatsapp"
label="WhatsApp konsultasi"
error={errors.whatsapp}
>
<Input
id="whatsapp"
name="whatsapp"
defaultValue={contact.whatsapp}
required
/>
</FormField>
<FormField
id="email_busdev"
label="Email business development"
error={errors.email_busdev}
>
<Input
id="email_busdev"
name="email_busdev"
type="email"
defaultValue={contact.email_busdev}
required
/>
</FormField>
<FormField
id="email_support"
label="Email support"
error={errors.email_support}
>
<Input
id="email_support"
name="email_support"
type="email"
defaultValue={contact.email_support}
required
/>
</FormField>
<div className="flex justify-end sm:col-span-2">
<Button type="submit" disabled={processing}>
Simpan Kontak
</Button>
</div>
</>
)}
</Form>
</section>
<section className="flex flex-col gap-4">
<div className="flex items-end justify-between gap-3">
<Heading
variant="small"
title="Kantor"
description="Diurutkan berdasarkan nomor urut."
/>
<OfficeDialog
nextOrder={offices.length + 1}
trigger={
<Button>
<Plus /> Tambah Kantor
</Button>
}
/>
</div>
<div className="grid gap-4 md:grid-cols-2">
{offices.map((office) => (
<div
key={office.id}
className="bg-card flex flex-col gap-3 rounded-xl border p-5"
>
<div className="flex items-start justify-between gap-2">
<div className="flex flex-col">
<span className="font-display font-bold">
{office.name}
</span>
<span className="text-muted-foreground text-xs">
{office.district} · {office.phone}
</span>
</div>
<div className="flex gap-1">
<OfficeDialog
office={office}
trigger={
<Button
variant="ghost"
size="icon"
aria-label="Ubah"
>
<Pencil />
</Button>
}
/>
<ConfirmDelete
title="Hapus kantor ini?"
description={`${office.name} tidak akan tampil lagi di website.`}
form={destroy.form(office.id)}
trigger={
<Button
variant="ghost"
size="icon"
aria-label="Hapus"
className="text-destructive"
>
<Trash2 />
</Button>
}
/>
</div>
</div>
<p className="flex gap-2 text-sm">
<MapPin className="text-primary mt-0.5 size-4 shrink-0" />
{officeAddress(office)}
</p>
{office.maps_url && (
<a
href={office.maps_url}
target="_blank"
rel="noopener noreferrer"
className="text-primary flex items-center gap-1 text-xs font-semibold hover:underline"
>
Buka di Google Maps
<ExternalLink className="size-3.5" />
</a>
)}
</div>
))}
</div>
</section>
</div>
</>
);
}
function OfficeDialog({
office,
nextOrder = 1,
trigger,
}: {
office?: Office;
nextOrder?: number;
trigger: ReactNode;
}) {
const id = (field: string) => `office-${office?.id ?? 'new'}-${field}`;
const text = (
field: keyof Office,
label: string,
errors: Partial<Record<string, string>>,
extra: { placeholder?: string; required?: boolean } = {
required: true,
},
) => (
<FormField id={id(field)} label={label} error={errors[field]}>
<Input
id={id(field)}
name={field}
defaultValue={office?.[field] ?? ''}
{...extra}
/>
</FormField>
);
return (
<FormDialog
trigger={trigger}
title={office ? 'Ubah Kantor' : 'Tambah Kantor'}
form={office ? update.form(office.id) : store.form()}
>
{(errors) => (
<>
{text('name', 'Nama kantor', errors)}
{text('building', 'Gedung / nomor', errors)}
{text('street', 'Jalan', errors)}
<div className="grid gap-4 sm:grid-cols-2">
{text('area', 'Kelurahan / kecamatan', errors)}
{text('city', 'Kota & kode pos', errors)}
{text('district', 'Label wilayah', errors, {
placeholder: 'Mis. Bintaro',
required: true,
})}
{text('phone', 'Telepon', errors)}
</div>
{text('maps_url', 'Link Google Maps (opsional)', errors, {
placeholder: 'https://maps.app.goo.gl/…',
})}
<div className="grid gap-4 sm:grid-cols-2">
{text('icon', 'Ikon (Material Symbols)', errors, {
placeholder: 'apartment',
required: true,
})}
<FormField
id={id('sort_order')}
label="Nomor urut"
error={errors.sort_order}
>
<Input
id={id('sort_order')}
name="sort_order"
type="number"
min={0}
defaultValue={office?.sort_order ?? nextOrder}
required
/>
</FormField>
</div>
</>
)}
</FormDialog>
);
}
CompanyEdit.layout = {
breadcrumbs: [{ title: 'Info Perusahaan', href: edit() }],
};