dress/resources/js/pages/admin/system/settings/index.tsx

263 lines
13 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 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 space-y-6">
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
<CardHeader className="border-b">
<CardTitle>Informasi Aplikasi</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<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="text-xs mt-1" />
</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="text-xs mt-1" />
</Field>
</CardContent>
</Card>
</div>
<div className="space-y-6">
<Card className="overflow-hidden border-none shadow-lg bg-card/50 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 object-cover rounded-xl border shadow"
/>
<button
type="button"
onClick={removeLogo}
className="absolute -top-2 -right-2 bg-red-500 hover:bg-red-600 text-white rounded-full w-6 h-6 flex items-center justify-center shadow-md transition-colors"
>
<X className="w-3.5 h-3.5" />
</button>
</div>
) : (
<button
type="button"
onClick={() => logoInputRef.current?.click()}
className="flex flex-col items-center justify-center w-full h-40 border-2 border-dashed border-border rounded-xl hover:border-primary hover:bg-primary/5 transition-all cursor-pointer group"
>
<ImagePlus className="w-10 h-10 text-muted-foreground group-hover:text-primary transition-colors mb-2" />
<span className="text-sm text-muted-foreground group-hover:text-primary transition-colors font-medium">Klik untuk upload logo</span>
<span className="text-xs text-muted-foreground mt-1">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="text-xs mt-2" />
</CardContent>
</Card>
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
<CardHeader className="border-b">
<CardTitle>Favicon / Icon</CardTitle>
</CardHeader>
<CardContent>
{iconPreview ? (
<div className="relative inline-block w-full flex justify-center">
<div className="relative">
<img
src={iconPreview}
alt="Icon preview"
className="size-24 object-contain rounded-xl border shadow p-2"
/>
<button
type="button"
onClick={removeIcon}
className="absolute -top-2 -right-2 bg-red-500 hover:bg-red-600 text-white rounded-full w-6 h-6 flex items-center justify-center shadow-md transition-colors"
>
<X className="w-3.5 h-3.5" />
</button>
</div>
</div>
) : (
<button
type="button"
onClick={() => iconInputRef.current?.click()}
className="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-border rounded-xl hover:border-primary hover:bg-primary/5 transition-all cursor-pointer group"
>
<ImagePlus className="w-8 h-8 text-muted-foreground group-hover:text-primary transition-colors mb-2" />
<span className="text-sm text-muted-foreground group-hover:text-primary transition-colors font-medium">Klik untuk upload icon</span>
<span className="text-xs text-muted-foreground mt-1">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="text-xs mt-2" />
</CardContent>
</Card>
<div className="flex flex-col gap-4 p-4 rounded-xl bg-primary/5 border border-primary/10 shadow-sm">
<Button type="submit" className="w-full gap-2" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</div>
</div>
</form>
</div>
);
}
SettingIndex.layout = {
breadcrumbs: [
{
title: 'Sistem',
}
],
};