330 lines
15 KiB
TypeScript
330 lines
15 KiB
TypeScript
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Field, FieldError } from '@/components/ui/field';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import systemRoutes from '@/routes/system';
|
|
import type { GeneralSetting } from '@/types';
|
|
import { Head, useForm } from '@inertiajs/react';
|
|
import { ImagePlus, Loader, Save, X } from 'lucide-react';
|
|
import React, { useRef, useState } from 'react';
|
|
import { toast } from 'sonner';
|
|
|
|
export default function SettingIndex({
|
|
setting,
|
|
}: {
|
|
setting: GeneralSetting | null;
|
|
}) {
|
|
const { data, setData, post, processing, errors } = useForm({
|
|
name: setting?.name || '',
|
|
description: setting?.description || '',
|
|
address: setting?.address || '',
|
|
phone: setting?.phone || '',
|
|
logo: null as File | null,
|
|
icon: null as File | null,
|
|
});
|
|
|
|
const logoInputRef = useRef<HTMLInputElement>(null);
|
|
const iconInputRef = useRef<HTMLInputElement>(null);
|
|
|
|
const [logoPreview, setLogoPreview] = useState<string | null>(
|
|
setting?.logo_url || null,
|
|
);
|
|
const [iconPreview, setIconPreview] = useState<string | null>(
|
|
setting?.icon_url || null,
|
|
);
|
|
|
|
const handleLogoChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0];
|
|
|
|
if (!file) {
|
|
return;
|
|
}
|
|
|
|
setData('logo', file);
|
|
const reader = new FileReader();
|
|
reader.onloadend = () => setLogoPreview(reader.result as string);
|
|
reader.readAsDataURL(file);
|
|
};
|
|
|
|
const removeLogo = () => {
|
|
setData('logo', null);
|
|
setLogoPreview(null);
|
|
|
|
if (logoInputRef.current) {
|
|
logoInputRef.current.value = '';
|
|
}
|
|
};
|
|
|
|
const handleIconChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0];
|
|
|
|
if (!file) {
|
|
return;
|
|
}
|
|
|
|
setData('icon', file);
|
|
const reader = new FileReader();
|
|
reader.onloadend = () => setIconPreview(reader.result as string);
|
|
reader.readAsDataURL(file);
|
|
};
|
|
|
|
const removeIcon = () => {
|
|
setData('icon', null);
|
|
setIconPreview(null);
|
|
|
|
if (iconInputRef.current) {
|
|
iconInputRef.current.value = '';
|
|
}
|
|
};
|
|
|
|
const onSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
post(systemRoutes.settings.update().url, {
|
|
forceFormData: true,
|
|
onSuccess: (response: any) => {
|
|
toast.success(response.props.flash.success);
|
|
},
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col gap-6 p-6">
|
|
<Head title="Pengaturan Umum" />
|
|
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold tracking-tight">
|
|
Pengaturan Umum
|
|
</h1>
|
|
</div>
|
|
</div>
|
|
|
|
<form onSubmit={onSubmit} className="space-y-6">
|
|
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3">
|
|
<div className="space-y-6 lg:col-span-2">
|
|
<Card className="overflow-hidden border-none bg-card/50 shadow-lg backdrop-blur-sm">
|
|
<CardHeader className="border-b">
|
|
<CardTitle>Informasi Aplikasi</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
<Field>
|
|
<Label htmlFor="phone" required>
|
|
No. Telepon
|
|
</Label>
|
|
<Input
|
|
id="phone"
|
|
value={data.phone || ''}
|
|
onChange={(e) =>
|
|
setData('phone', e.target.value)
|
|
}
|
|
placeholder="0812xxxx"
|
|
/>
|
|
<FieldError
|
|
error={errors.phone}
|
|
label="No. Telepon"
|
|
className="text-xs"
|
|
/>
|
|
</Field>
|
|
|
|
<Field>
|
|
<Label htmlFor="name" required>
|
|
Nama Aplikasi
|
|
</Label>
|
|
<Input
|
|
id="name"
|
|
name="name"
|
|
value={data.name}
|
|
onChange={(e) =>
|
|
setData('name', e.target.value)
|
|
}
|
|
autoComplete="off"
|
|
placeholder="Contoh: VN Grup Dress"
|
|
maxLength={100}
|
|
/>
|
|
<FieldError
|
|
error={errors.name}
|
|
label="Nama Aplikasi"
|
|
className="text-xs"
|
|
/>
|
|
</Field>
|
|
</div>
|
|
|
|
<Field>
|
|
<Label htmlFor="description" required>
|
|
Deskripsi
|
|
</Label>
|
|
<Textarea
|
|
id="description"
|
|
value={data.description || ''}
|
|
onChange={(e) =>
|
|
setData(
|
|
'description',
|
|
e.target.value,
|
|
)
|
|
}
|
|
placeholder="Deskripsi singkat aplikasi"
|
|
rows={4}
|
|
/>
|
|
<FieldError
|
|
error={errors.description}
|
|
label="Deskripsi"
|
|
className="mt-1 text-xs"
|
|
/>
|
|
</Field>
|
|
|
|
<Field>
|
|
<Label htmlFor="address" required>
|
|
Alamat
|
|
</Label>
|
|
<Textarea
|
|
id="address"
|
|
value={data.address || ''}
|
|
onChange={(e) =>
|
|
setData('address', e.target.value)
|
|
}
|
|
placeholder="Alamat lengkap toko"
|
|
rows={3}
|
|
/>
|
|
<FieldError
|
|
error={errors.address}
|
|
label="Alamat"
|
|
className="mt-1 text-xs"
|
|
/>
|
|
</Field>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
<div className="space-y-6">
|
|
<Card className="overflow-hidden border-none bg-card/50 shadow-lg backdrop-blur-sm">
|
|
<CardHeader className="border-b">
|
|
<CardTitle>Logo Aplikasi</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{logoPreview ? (
|
|
<div className="relative inline-block w-full">
|
|
<img
|
|
src={logoPreview}
|
|
alt="Logo preview"
|
|
className="w-full rounded-xl border object-cover shadow"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={removeLogo}
|
|
className="absolute -top-2 -right-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white shadow-md transition-colors hover:bg-red-600"
|
|
>
|
|
<X className="h-3.5 w-3.5" />
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
logoInputRef.current?.click()
|
|
}
|
|
className="group flex h-40 w-full cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed border-border transition-all hover:border-primary hover:bg-primary/5"
|
|
>
|
|
<ImagePlus className="mb-2 h-10 w-10 text-muted-foreground transition-colors group-hover:text-primary" />
|
|
<span className="text-sm font-medium text-muted-foreground transition-colors group-hover:text-primary">
|
|
Klik untuk upload logo
|
|
</span>
|
|
<span className="mt-1 text-xs text-muted-foreground">
|
|
PNG, JPG, WEBP — maks. 2MB
|
|
</span>
|
|
</button>
|
|
)}
|
|
<input
|
|
ref={logoInputRef}
|
|
type="file"
|
|
accept="image/*"
|
|
className="hidden"
|
|
onChange={handleLogoChange}
|
|
/>
|
|
<FieldError
|
|
error={errors.logo}
|
|
label="Logo Aplikasi"
|
|
className="mt-2 text-xs"
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card className="overflow-hidden border-none bg-card/50 shadow-lg backdrop-blur-sm">
|
|
<CardHeader className="border-b">
|
|
<CardTitle>Favicon / Icon</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{iconPreview ? (
|
|
<div className="relative flex inline-block w-full justify-center">
|
|
<div className="relative">
|
|
<img
|
|
src={iconPreview}
|
|
alt="Icon preview"
|
|
className="size-24 rounded-xl border object-contain p-2 shadow"
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={removeIcon}
|
|
className="absolute -top-2 -right-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-white shadow-md transition-colors hover:bg-red-600"
|
|
>
|
|
<X className="h-3.5 w-3.5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
iconInputRef.current?.click()
|
|
}
|
|
className="group flex h-32 w-full cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed border-border transition-all hover:border-primary hover:bg-primary/5"
|
|
>
|
|
<ImagePlus className="mb-2 h-8 w-8 text-muted-foreground transition-colors group-hover:text-primary" />
|
|
<span className="text-sm font-medium text-muted-foreground transition-colors group-hover:text-primary">
|
|
Klik untuk upload icon
|
|
</span>
|
|
<span className="mt-1 text-xs text-muted-foreground">
|
|
ICO, PNG — maks. 1MB
|
|
</span>
|
|
</button>
|
|
)}
|
|
<input
|
|
ref={iconInputRef}
|
|
type="file"
|
|
accept="image/*"
|
|
className="hidden"
|
|
onChange={handleIconChange}
|
|
/>
|
|
<FieldError
|
|
error={errors.icon}
|
|
label="Favicon / Icon"
|
|
className="mt-2 text-xs"
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
|
|
<Button type="submit" disabled={processing}>
|
|
{processing ? (
|
|
<Loader className="size-4 animate-spin" />
|
|
) : (
|
|
<Save className="size-4" />
|
|
)}
|
|
{processing ? 'Menyimpan...' : 'Simpan'}
|
|
</Button>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
SettingIndex.layout = {
|
|
breadcrumbs: [
|
|
{
|
|
title: 'Sistem',
|
|
},
|
|
],
|
|
};
|