- Integrated toast notifications to display error messages when form submissions fail across various components, enhancing user feedback and experience. - Updated components including DeleteUser, FormDialog, ManageTwoFactor, TwoFactorRecoveryCodes, TwoFactorSetupModal, PayrollPeriodShow, EmployeeCreate, EmployeeEdit, CuttingCreate, CuttingEdit, PurchaseCreate, PurchaseEdit, RestockCreate, RestockEdit, TransactionCreate, TransactionEdit, Category management, Product management, Raw Material management, Role management, Settings, and Authentication pages.
922 lines
44 KiB
TypeScript
922 lines
44 KiB
TypeScript
import { Form, Head } from '@inertiajs/react';
|
|
import { Plus, Trash2 } from 'lucide-react';
|
|
import { useState } from 'react';
|
|
import { toast } from 'sonner';
|
|
import { FileUpload } from '@/components/file-upload';
|
|
import InputError from '@/components/input-error';
|
|
import { PhoneNumberInput } from '@/components/phone-number-input';
|
|
import { RupiahInput } from '@/components/rupiah-input';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
|
import { Separator } from '@/components/ui/separator';
|
|
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import { cn } from '@/lib/utils';
|
|
import {
|
|
updateHomepage,
|
|
updateHr,
|
|
updateMarketplace,
|
|
updateSocialMedia,
|
|
updateSystem,
|
|
} from '@/routes/admin/settings';
|
|
|
|
type MarketplaceFeeRule = {
|
|
base: string;
|
|
type: string;
|
|
value: number;
|
|
};
|
|
|
|
type Props = {
|
|
system: {
|
|
app_name: string;
|
|
about_app: string;
|
|
address: string;
|
|
email: string;
|
|
phone: string;
|
|
};
|
|
homepage: {
|
|
hero_image_url: string;
|
|
hero_image_key: string;
|
|
about_image_url: string;
|
|
about_image_key: string;
|
|
gallery_images: string[];
|
|
gallery_image_keys: string[];
|
|
};
|
|
socialMedia: {
|
|
instagram_url: string;
|
|
facebook_url: string;
|
|
tiktok_url: string;
|
|
};
|
|
marketplace: {
|
|
tiktok_shop: Record<string, MarketplaceFeeRule>;
|
|
shopee: Record<string, MarketplaceFeeRule>;
|
|
};
|
|
hr: {
|
|
scheduled_check_in_time: string;
|
|
scheduled_check_out_time: string;
|
|
late_penalty_amount: number;
|
|
absent_penalty_amount: number;
|
|
};
|
|
};
|
|
|
|
type HomepageSettingsTabProps = {
|
|
homepage: Props['homepage'];
|
|
};
|
|
|
|
function HomepageSettingsTab({ homepage }: HomepageSettingsTabProps) {
|
|
const [heroKey, setHeroKey] = useState<string | null>(
|
|
homepage.hero_image_key ?? null,
|
|
);
|
|
const [heroUploading, setHeroUploading] = useState(false);
|
|
const [aboutKey, setAboutKey] = useState<string | null>(
|
|
homepage.about_image_key ?? null,
|
|
);
|
|
const [aboutUploading, setAboutUploading] = useState(false);
|
|
const [galleryKeys, setGalleryKeys] = useState<string[]>(
|
|
homepage.gallery_image_keys ?? [],
|
|
);
|
|
const [galleryUploading, setGalleryUploading] = useState(false);
|
|
|
|
function addGalleryImage() {
|
|
setGalleryKeys((prev) => [...prev, '']);
|
|
}
|
|
|
|
function removeGalleryImage(index: number) {
|
|
setGalleryKeys((prev) => prev.filter((_, i) => i !== index));
|
|
}
|
|
|
|
function updateGalleryKey(index: number, key: string | null) {
|
|
setGalleryKeys((prev) =>
|
|
prev.map((k, i) => (i === index ? (key ?? '') : k)),
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Form
|
|
action={updateHomepage()}
|
|
options={{ preserveScroll: true }}
|
|
onError={() => {
|
|
toast.error('Terjadi kesalahan saat menyimpan data. Silakan periksa kembali input Anda.');
|
|
}}
|
|
>
|
|
{({ processing, errors }) => (
|
|
<div className="grid gap-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Homepage</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="grid grid-cols-1 gap-4">
|
|
<input
|
|
type="hidden"
|
|
name="hero_image_key"
|
|
value={heroKey ?? ''}
|
|
/>
|
|
<input
|
|
type="hidden"
|
|
name="about_image_key"
|
|
value={aboutKey ?? ''}
|
|
/>
|
|
{galleryKeys.map((key, index) => (
|
|
<input
|
|
key={index}
|
|
type="hidden"
|
|
name={`gallery_image_keys[${index}]`}
|
|
value={key}
|
|
/>
|
|
))}
|
|
|
|
<div className="grid gap-2">
|
|
<Label>Foto Hero</Label>
|
|
<FileUpload
|
|
value={heroKey}
|
|
onChange={setHeroKey}
|
|
folder="homepage/hero"
|
|
existingUrl={homepage.hero_image_url}
|
|
onUploadingChange={setHeroUploading}
|
|
/>
|
|
<InputError message={errors.hero_image_key} />
|
|
</div>
|
|
|
|
<div className="grid gap-2">
|
|
<Label>Foto Tentang Kami</Label>
|
|
<FileUpload
|
|
value={aboutKey}
|
|
onChange={setAboutKey}
|
|
folder="homepage/about"
|
|
existingUrl={homepage.about_image_url}
|
|
onUploadingChange={setAboutUploading}
|
|
/>
|
|
<InputError message={errors.about_image_key} />
|
|
</div>
|
|
|
|
<div className="grid gap-2">
|
|
<div className="flex items-center justify-between">
|
|
<Label>Galeri Foto</Label>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={addGalleryImage}
|
|
>
|
|
<Plus className="h-4 w-4" />
|
|
Tambah
|
|
</Button>
|
|
</div>
|
|
<div className="grid gap-3">
|
|
{galleryKeys.map((key, index) => (
|
|
<div
|
|
key={index}
|
|
className="flex items-start gap-2"
|
|
>
|
|
<div className="flex-1">
|
|
<FileUpload
|
|
value={key || null}
|
|
onChange={(k) =>
|
|
updateGalleryKey(
|
|
index,
|
|
k,
|
|
)
|
|
}
|
|
folder="homepage/gallery"
|
|
existingUrl={
|
|
homepage.gallery_images[
|
|
index
|
|
] ?? null
|
|
}
|
|
onUploadingChange={
|
|
setGalleryUploading
|
|
}
|
|
/>
|
|
</div>
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="icon"
|
|
className="mt-1 text-destructive hover:text-destructive"
|
|
onClick={() =>
|
|
removeGalleryImage(index)
|
|
}
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
))}
|
|
{galleryKeys.length === 0 && (
|
|
<p className="text-sm text-muted-foreground">
|
|
Belum ada gambar. Klik "Tambah"
|
|
untuk menambahkan gambar galeri.
|
|
</p>
|
|
)}
|
|
</div>
|
|
<InputError
|
|
message={errors.gallery_image_keys}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<div className="flex items-center gap-4">
|
|
<Button
|
|
type="submit"
|
|
disabled={
|
|
processing ||
|
|
heroUploading ||
|
|
aboutUploading ||
|
|
galleryUploading
|
|
}
|
|
>
|
|
{processing ? 'Menyimpan...' : 'Simpan'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Form>
|
|
);
|
|
}
|
|
|
|
const sidebarTabs = [
|
|
{ key: 'sistem', label: 'Sistem' },
|
|
{ key: 'homepage', label: 'Homepage' },
|
|
{ key: 'media-sosial', label: 'Media Sosial' },
|
|
{ key: 'marketplace', label: 'Marketplace' },
|
|
{ key: 'hr', label: 'HR' },
|
|
] as const;
|
|
|
|
type TabKey = (typeof sidebarTabs)[number]['key'];
|
|
|
|
function MarketplaceVariableInput({
|
|
label,
|
|
prefix,
|
|
data,
|
|
}: {
|
|
label: string;
|
|
prefix: string;
|
|
data: MarketplaceFeeRule;
|
|
}) {
|
|
const [type, setType] = useState(data.type);
|
|
|
|
return (
|
|
<div className="flex flex-col gap-3 rounded-md border p-3 md:flex-row md:items-start md:gap-4">
|
|
<span className="min-w-[160px] text-sm font-medium md:pt-2">
|
|
{label}
|
|
</span>
|
|
<div className="grid flex-1 grid-cols-1 gap-3 md:grid-cols-3">
|
|
<div className="grid gap-2">
|
|
<span className="text-xs font-medium text-muted-foreground md:hidden">
|
|
Dasar
|
|
</span>
|
|
<RadioGroup
|
|
name={`${prefix}[base]`}
|
|
defaultValue={data.base}
|
|
className="flex gap-4"
|
|
>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem
|
|
value="per_transaksi"
|
|
id={`${prefix}-base-per_transaksi`}
|
|
/>
|
|
<Label
|
|
htmlFor={`${prefix}-base-per_transaksi`}
|
|
className="text-xs font-normal"
|
|
>
|
|
Per Transaksi
|
|
</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem
|
|
value="per_produk"
|
|
id={`${prefix}-base-per_produk`}
|
|
/>
|
|
<Label
|
|
htmlFor={`${prefix}-base-per_produk`}
|
|
className="text-xs font-normal"
|
|
>
|
|
Per Produk
|
|
</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<span className="text-xs font-medium text-muted-foreground md:hidden">
|
|
Tipe
|
|
</span>
|
|
<RadioGroup
|
|
name={`${prefix}[type]`}
|
|
defaultValue={data.type}
|
|
onValueChange={setType}
|
|
className="flex gap-4"
|
|
>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem
|
|
value="flat"
|
|
id={`${prefix}-type-flat`}
|
|
/>
|
|
<Label
|
|
htmlFor={`${prefix}-type-flat`}
|
|
className="text-xs font-normal"
|
|
>
|
|
Flat
|
|
</Label>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<RadioGroupItem
|
|
value="persentase"
|
|
id={`${prefix}-type-persentase`}
|
|
/>
|
|
<Label
|
|
htmlFor={`${prefix}-type-persentase`}
|
|
className="text-xs font-normal"
|
|
>
|
|
Persentase
|
|
</Label>
|
|
</div>
|
|
</RadioGroup>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<span className="text-xs font-medium text-muted-foreground md:hidden">
|
|
Nilai
|
|
</span>
|
|
{type === 'flat' ? (
|
|
<RupiahInput
|
|
name={`${prefix}[value]`}
|
|
defaultValue={data.value}
|
|
/>
|
|
) : (
|
|
<Input
|
|
name={`${prefix}[value]`}
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
defaultValue={data.value}
|
|
placeholder="0"
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function MarketplaceColumnHeader() {
|
|
return (
|
|
<div className="hidden gap-3 px-3 pb-1 text-xs font-medium text-muted-foreground md:grid md:grid-cols-[160px_1fr] md:gap-4">
|
|
<span>Biaya</span>
|
|
<div className="grid flex-1 grid-cols-3 gap-3">
|
|
<span>Dasar</span>
|
|
<span>Tipe</span>
|
|
<span>Nilai</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function MarketplaceCard({
|
|
title,
|
|
variables,
|
|
}: {
|
|
title: string;
|
|
variables: { label: string; prefix: string; data: MarketplaceFeeRule }[];
|
|
}) {
|
|
return (
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="text-base">{title}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="grid grid-cols-1 gap-2">
|
|
<MarketplaceColumnHeader />
|
|
{variables.map((v) => (
|
|
<MarketplaceVariableInput key={v.prefix} {...v} />
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
export default function AdminSettings({
|
|
system,
|
|
homepage,
|
|
socialMedia,
|
|
marketplace,
|
|
hr,
|
|
}: Props) {
|
|
const [activeTab, setActiveTab] = useState<TabKey>('sistem');
|
|
const [marketplaceTab, setMarketplaceTab] = useState<
|
|
'tiktok-shop' | 'shopee'
|
|
>('tiktok-shop');
|
|
|
|
const tiktokShopVariables = [
|
|
{
|
|
label: 'Komisi Platform',
|
|
prefix: 'tiktok_shop_platform_commission',
|
|
data: marketplace.tiktok_shop.platform_commission,
|
|
},
|
|
{
|
|
label: 'Layanan Logistik',
|
|
prefix: 'tiktok_shop_logistics_service_fee',
|
|
data: marketplace.tiktok_shop.logistics_service_fee,
|
|
},
|
|
{
|
|
label: 'Komisi Dinamis',
|
|
prefix: 'tiktok_shop_dynamic_commission',
|
|
data: marketplace.tiktok_shop.dynamic_commission,
|
|
},
|
|
{
|
|
label: 'Pemrosesan Pesanan',
|
|
prefix: 'tiktok_shop_order_processing_fee',
|
|
data: marketplace.tiktok_shop.order_processing_fee,
|
|
},
|
|
{
|
|
label: 'Affiliate',
|
|
prefix: 'tiktok_shop_affiliate',
|
|
data: marketplace.tiktok_shop.affiliate,
|
|
},
|
|
{
|
|
label: 'Layanan PO',
|
|
prefix: 'tiktok_shop_pre_order_service_fee',
|
|
data: marketplace.tiktok_shop.pre_order_service_fee,
|
|
},
|
|
];
|
|
|
|
const shopeeVariables = [
|
|
{
|
|
label: 'Biaya Administrasi',
|
|
prefix: 'shopee_admin_fee',
|
|
data: marketplace.shopee.admin_fee,
|
|
},
|
|
{
|
|
label: 'Biaya Program',
|
|
prefix: 'shopee_program_fee',
|
|
data: marketplace.shopee.program_fee,
|
|
},
|
|
{
|
|
label: 'Hemat Biaya Kirim',
|
|
prefix: 'shopee_shipping_savings',
|
|
data: marketplace.shopee.shipping_savings,
|
|
},
|
|
{
|
|
label: 'Premi',
|
|
prefix: 'shopee_premium',
|
|
data: marketplace.shopee.premium,
|
|
},
|
|
{
|
|
label: 'Biaya Layanan',
|
|
prefix: 'shopee_service_fee',
|
|
data: marketplace.shopee.service_fee,
|
|
},
|
|
{
|
|
label: 'Biaya Proses Pesanan',
|
|
prefix: 'shopee_order_processing_fee',
|
|
data: marketplace.shopee.order_processing_fee,
|
|
},
|
|
{
|
|
label: 'Biaya Komisi AMS',
|
|
prefix: 'shopee_ams_commission_fee',
|
|
data: marketplace.shopee.ams_commission_fee,
|
|
},
|
|
{
|
|
label: 'PO',
|
|
prefix: 'shopee_pre_order',
|
|
data: marketplace.shopee.pre_order,
|
|
},
|
|
{
|
|
label: 'Live Extra',
|
|
prefix: 'shopee_live_extra',
|
|
data: marketplace.shopee.live_extra,
|
|
},
|
|
];
|
|
|
|
return (
|
|
<>
|
|
<Head title="Pengaturan" />
|
|
|
|
<div className="flex h-full flex-1 flex-col gap-6 overflow-x-auto p-4 md:p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h2 className="text-2xl font-semibold tracking-tight">
|
|
Pengaturan
|
|
</h2>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-col lg:flex-row lg:space-x-12">
|
|
<aside className="w-full max-w-xl lg:w-48">
|
|
<nav
|
|
className="flex flex-col space-y-1 space-x-0"
|
|
aria-label="Admin Settings"
|
|
>
|
|
{sidebarTabs.map((tab) => (
|
|
<Button
|
|
key={tab.key}
|
|
size="sm"
|
|
variant="ghost"
|
|
onClick={() => setActiveTab(tab.key)}
|
|
className={cn('w-full justify-start', {
|
|
'bg-muted': activeTab === tab.key,
|
|
})}
|
|
>
|
|
{tab.label}
|
|
</Button>
|
|
))}
|
|
</nav>
|
|
</aside>
|
|
|
|
<Separator className="my-6 lg:hidden" />
|
|
|
|
<section className="w-full min-w-0 flex-1 space-y-6">
|
|
{activeTab === 'sistem' && (
|
|
<Form
|
|
action={updateSystem()}
|
|
options={{ preserveScroll: true }}
|
|
onError={() => {
|
|
toast.error('Terjadi kesalahan saat menyimpan data. Silakan periksa kembali input Anda.');
|
|
}}
|
|
>
|
|
{({ processing, errors }) => (
|
|
<div className="grid gap-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Sistem</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
<div className="grid gap-2 md:col-span-2">
|
|
<Label htmlFor="app_name">
|
|
Nama Aplikasi{' '}
|
|
<span className="text-destructive">
|
|
*
|
|
</span>
|
|
</Label>
|
|
<Input
|
|
id="app_name"
|
|
name="app_name"
|
|
placeholder="Masukkan nama aplikasi"
|
|
defaultValue={
|
|
system.app_name
|
|
}
|
|
/>
|
|
<InputError
|
|
message={
|
|
errors.app_name
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="email">
|
|
Email
|
|
</Label>
|
|
<Input
|
|
id="email"
|
|
name="email"
|
|
type="email"
|
|
placeholder="Masukkan email"
|
|
defaultValue={
|
|
system.email
|
|
}
|
|
/>
|
|
<InputError
|
|
message={errors.email}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="phone">
|
|
No. Telepon
|
|
</Label>
|
|
<PhoneNumberInput
|
|
name="phone"
|
|
defaultValue={
|
|
system.phone
|
|
}
|
|
/>
|
|
<InputError
|
|
message={errors.phone}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2 md:col-span-2">
|
|
<Label htmlFor="address">
|
|
Alamat
|
|
</Label>
|
|
<Textarea
|
|
id="address"
|
|
name="address"
|
|
placeholder="Masukkan alamat"
|
|
rows={3}
|
|
defaultValue={
|
|
system.address
|
|
}
|
|
/>
|
|
<InputError
|
|
message={errors.address}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2 md:col-span-2">
|
|
<Label htmlFor="about_app">
|
|
Tentang Aplikasi
|
|
</Label>
|
|
<Textarea
|
|
id="about_app"
|
|
name="about_app"
|
|
placeholder="Masukkan deskripsi aplikasi"
|
|
rows={4}
|
|
defaultValue={
|
|
system.about_app
|
|
}
|
|
/>
|
|
<InputError
|
|
message={
|
|
errors.about_app
|
|
}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<div className="flex items-center gap-4">
|
|
<Button
|
|
type="submit"
|
|
disabled={processing}
|
|
>
|
|
{processing
|
|
? 'Menyimpan...'
|
|
: 'Simpan'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Form>
|
|
)}
|
|
|
|
{activeTab === 'homepage' && (
|
|
<HomepageSettingsTab homepage={homepage} />
|
|
)}
|
|
|
|
{activeTab === 'media-sosial' && (
|
|
<Form
|
|
action={updateSocialMedia()}
|
|
options={{ preserveScroll: true }}
|
|
onError={() => {
|
|
toast.error('Terjadi kesalahan saat menyimpan data. Silakan periksa kembali input Anda.');
|
|
}}
|
|
>
|
|
{({ processing, errors }) => (
|
|
<div className="grid gap-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>
|
|
Media Sosial
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="instagram_url">
|
|
Instagram
|
|
</Label>
|
|
<Input
|
|
id="instagram_url"
|
|
name="instagram_url"
|
|
placeholder="https://instagram.com/..."
|
|
defaultValue={
|
|
socialMedia.instagram_url ??
|
|
''
|
|
}
|
|
/>
|
|
<InputError
|
|
message={
|
|
errors.instagram_url
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="facebook_url">
|
|
Facebook
|
|
</Label>
|
|
<Input
|
|
id="facebook_url"
|
|
name="facebook_url"
|
|
placeholder="https://facebook.com/..."
|
|
defaultValue={
|
|
socialMedia.facebook_url ??
|
|
''
|
|
}
|
|
/>
|
|
<InputError
|
|
message={
|
|
errors.facebook_url
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="tiktok_url">
|
|
TikTok
|
|
</Label>
|
|
<Input
|
|
id="tiktok_url"
|
|
name="tiktok_url"
|
|
placeholder="https://tiktok.com/..."
|
|
defaultValue={
|
|
socialMedia.tiktok_url ??
|
|
''
|
|
}
|
|
/>
|
|
<InputError
|
|
message={
|
|
errors.tiktok_url
|
|
}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<div className="flex items-center gap-4">
|
|
<Button
|
|
type="submit"
|
|
disabled={processing}
|
|
>
|
|
{processing
|
|
? 'Menyimpan...'
|
|
: 'Simpan'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Form>
|
|
)}
|
|
|
|
{activeTab === 'marketplace' && (
|
|
<Form
|
|
action={updateMarketplace()}
|
|
options={{ preserveScroll: true }}
|
|
onError={() => {
|
|
toast.error('Terjadi kesalahan saat menyimpan data. Silakan periksa kembali input Anda.');
|
|
}}
|
|
>
|
|
{({ processing }) => (
|
|
<div className="grid gap-6">
|
|
<Tabs
|
|
value={marketplaceTab}
|
|
onValueChange={(v) =>
|
|
setMarketplaceTab(
|
|
v as
|
|
| 'tiktok-shop'
|
|
| 'shopee',
|
|
)
|
|
}
|
|
>
|
|
<TabsList className="mb-3">
|
|
<TabsTrigger value="tiktok-shop">
|
|
TikTok Shop
|
|
</TabsTrigger>
|
|
<TabsTrigger value="shopee">
|
|
Shopee
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
</Tabs>
|
|
<div
|
|
hidden={
|
|
marketplaceTab !== 'tiktok-shop'
|
|
}
|
|
>
|
|
<MarketplaceCard
|
|
title="TikTok Shop"
|
|
variables={tiktokShopVariables}
|
|
/>
|
|
</div>
|
|
<div
|
|
hidden={marketplaceTab !== 'shopee'}
|
|
>
|
|
<MarketplaceCard
|
|
title="Shopee"
|
|
variables={shopeeVariables}
|
|
/>
|
|
</div>
|
|
<div className="flex items-center gap-4">
|
|
<Button
|
|
type="submit"
|
|
disabled={processing}
|
|
>
|
|
{processing
|
|
? 'Menyimpan...'
|
|
: 'Simpan'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Form>
|
|
)}
|
|
|
|
{activeTab === 'hr' && (
|
|
<Form
|
|
action={updateHr()}
|
|
options={{ preserveScroll: true }}
|
|
onError={() => {
|
|
toast.error('Terjadi kesalahan saat menyimpan data. Silakan periksa kembali input Anda.');
|
|
}}
|
|
>
|
|
{({ processing, errors }) => (
|
|
<div className="grid gap-6">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>HR</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="scheduled_check_in_time">
|
|
Jam Masuk Kerja{' '}
|
|
<span className="text-destructive">
|
|
*
|
|
</span>
|
|
</Label>
|
|
<Input
|
|
id="scheduled_check_in_time"
|
|
name="scheduled_check_in_time"
|
|
type="time"
|
|
defaultValue={
|
|
hr.scheduled_check_in_time
|
|
}
|
|
/>
|
|
<InputError
|
|
message={
|
|
errors.scheduled_check_in_time
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label htmlFor="scheduled_check_out_time">
|
|
Jam Pulang Kerja{' '}
|
|
<span className="text-destructive">
|
|
*
|
|
</span>
|
|
</Label>
|
|
<Input
|
|
id="scheduled_check_out_time"
|
|
name="scheduled_check_out_time"
|
|
type="time"
|
|
defaultValue={
|
|
hr.scheduled_check_out_time
|
|
}
|
|
/>
|
|
<InputError
|
|
message={
|
|
errors.scheduled_check_out_time
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>
|
|
Denda Keterlambatan{' '}
|
|
<span className="text-destructive">
|
|
*
|
|
</span>
|
|
</Label>
|
|
<RupiahInput
|
|
name="late_penalty_amount"
|
|
defaultValue={
|
|
hr.late_penalty_amount
|
|
}
|
|
/>
|
|
<InputError
|
|
message={
|
|
errors.late_penalty_amount
|
|
}
|
|
/>
|
|
</div>
|
|
<div className="grid gap-2">
|
|
<Label>
|
|
Denda Bolos{' '}
|
|
<span className="text-destructive">
|
|
*
|
|
</span>
|
|
</Label>
|
|
<RupiahInput
|
|
name="absent_penalty_amount"
|
|
defaultValue={
|
|
hr.absent_penalty_amount
|
|
}
|
|
/>
|
|
<InputError
|
|
message={
|
|
errors.absent_penalty_amount
|
|
}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<div className="flex items-center gap-4">
|
|
<Button
|
|
type="submit"
|
|
disabled={processing}
|
|
>
|
|
{processing
|
|
? 'Menyimpan...'
|
|
: 'Simpan'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Form>
|
|
)}
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|