feat: update product create and edit forms to include detailed pricing fields and improved layout structure

This commit is contained in:
Yoga Pangestu 2026-04-17 09:54:53 +07:00
parent 2e1aebae97
commit e8ae633509
2 changed files with 334 additions and 275 deletions

View File

@ -1,5 +1,5 @@
import { Head, useForm, Link } from '@inertiajs/react'; import { Head, useForm, Link } from '@inertiajs/react';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Field, FieldGroup } from "@/components/ui/field" import { Field, FieldGroup } from "@/components/ui/field"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
@ -58,12 +58,21 @@ export default function ProductCreate({ categories }: { categories: Category[] }
<div> <div>
<h1 className="text-3xl font-bold tracking-tight">Tambah Produk</h1> <h1 className="text-3xl font-bold tracking-tight">Tambah Produk</h1>
</div> </div>
<Link href={productRoutes.index().url}>
<Button variant='outline'>
Kembali
</Button>
</Link>
</div> </div>
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm p-5"> <form onSubmit={onSubmit} className="space-y-6">
<CardContent className="p-0"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<form onSubmit={onSubmit} className="space-y-6 mt-4"> <div className="lg:col-span-2 space-y-6">
<FieldGroup> <Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
<CardHeader className="border-b">
<CardTitle>Informasi Produk</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
<Field> <Field>
<Label htmlFor="name">Nama</Label> <Label htmlFor="name">Nama</Label>
<Input <Input
@ -77,50 +86,15 @@ export default function ProductCreate({ categories }: { categories: Category[] }
/> />
{errors.name && <p className="text-xs text-red-500">{errors.name}</p>} {errors.name && <p className="text-xs text-red-500">{errors.name}</p>}
</Field> </Field>
</CardContent>
</Card>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
<Field> <CardHeader className="border-b">
<Label>Kategori</Label> <CardTitle>Harga Produk</CardTitle>
<Combobox </CardHeader>
multiple <CardContent>
autoHighlight <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
items={filteredCategories}
value={categories.filter(c => data.category_ids.includes(c.id))}
onValueChange={(selected) => {
setData(
"category_ids",
selected.map((item: Category) => item.id)
)
}}
>
<ComboboxChips ref={anchor} className="w-full">
<ComboboxValue>
{(values: Category[]) => (
<>
{values.map((value) => (
<ComboboxChip key={value.id}>
{value.name}
</ComboboxChip>
))}
<ComboboxChipsInput placeholder='Pilih Kategori' />
</>
)}
</ComboboxValue>
</ComboboxChips>
<ComboboxContent anchor={anchor}>
<ComboboxEmpty>Data tidak ditemukan.</ComboboxEmpty>
<ComboboxList>
{(item: Category) => (
<ComboboxItem key={item.id} value={item}>
{item.name}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
{errors.category_ids && <p className="text-xs text-red-500">{errors.category_ids}</p>}
</Field>
<Field> <Field>
<Label htmlFor="price_purchase">Harga Beli</Label> <Label htmlFor="price_purchase">Harga Beli</Label>
<NumericFormat <NumericFormat
@ -201,7 +175,7 @@ export default function ProductCreate({ categories }: { categories: Category[] }
{errors['prices.reseller'] && <p className="text-xs text-red-500">{errors['prices.reseller']}</p>} {errors['prices.reseller'] && <p className="text-xs text-red-500">{errors['prices.reseller']}</p>}
</Field> </Field>
<Field> <Field className="md:col-span-2">
<Label htmlFor="price_retail">Harga Retail</Label> <Label htmlFor="price_retail">Harga Retail</Label>
<NumericFormat <NumericFormat
id="price_retail" id="price_retail"
@ -221,28 +195,82 @@ export default function ProductCreate({ categories }: { categories: Category[] }
{errors['prices.retail'] && <p className="text-xs text-red-500">{errors['prices.retail']}</p>} {errors['prices.retail'] && <p className="text-xs text-red-500">{errors['prices.retail']}</p>}
</Field> </Field>
</div> </div>
</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>Kategori</CardTitle>
</CardHeader>
<CardContent>
<Field>
<Combobox
multiple
autoHighlight
items={filteredCategories}
value={categories.filter(c => data.category_ids.includes(c.id))}
onValueChange={(selected) => {
setData(
"category_ids",
selected.map((item: Category) => item.id)
)
}}
>
<ComboboxChips ref={anchor} className="w-full">
<ComboboxValue>
{(values: Category[]) => (
<>
{values.map((value) => (
<ComboboxChip key={value.id}>
{value.name}
</ComboboxChip>
))}
<ComboboxChipsInput placeholder='Pilih Kategori' />
</>
)}
</ComboboxValue>
</ComboboxChips>
<ComboboxContent anchor={anchor}>
<ComboboxEmpty>Data tidak ditemukan.</ComboboxEmpty>
<ComboboxList>
{(item: Category) => (
<ComboboxItem key={item.id} value={item}>
{item.name}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
{errors.category_ids && <p className="text-xs text-red-500 mt-1">{errors.category_ids}</p>}
</Field>
</CardContent>
</Card>
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
<CardHeader className="border-b">
<CardTitle>Deskripsi</CardTitle>
</CardHeader>
<CardContent>
<Field> <Field>
<Label htmlFor="description">Deskripsi</Label>
<SimpleEditor <SimpleEditor
value={data.description} value={data.description}
onChange={(val) => setData('description', val)} onChange={(val) => setData('description', val)}
/> />
{errors.description && <p className="text-xs text-red-500">{errors.description}</p>} {errors.description && <p className="text-xs text-red-500 mt-1">{errors.description}</p>}
</Field> </Field>
</FieldGroup> </CardContent>
</Card>
<div className="flex gap-4 p-4 mt-6 rounded-lg justify-end"> <div className="flex flex-col gap-4 p-4 rounded-xl bg-primary/5 border border-primary/10 shadow-sm">
<Link href={productRoutes.index().url}> <Button type="submit" className="w-full" disabled={processing}>
<Button type="button" variant="outline">Kembali</Button>
</Link>
<Button type="submit" disabled={processing}>
{processing ? 'Menyimpan...' : 'Simpan'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</div> </div>
</div>
</div>
</form> </form>
</CardContent>
</Card>
</div> </div>
); );

View File

@ -1,7 +1,7 @@
import { Head, useForm, Link } from '@inertiajs/react'; import { Head, useForm, Link } from '@inertiajs/react';
import type { Product, ProductPrice } from '@/types'; import type { Product, ProductPrice } from '@/types';
import { Category } from '@/types/category'; import { Category } from '@/types/category';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Field, FieldGroup } from "@/components/ui/field" import { Field, FieldGroup } from "@/components/ui/field"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
@ -66,12 +66,21 @@ export default function ProductEdit({ product, categories }: { product: Product,
<div> <div>
<h1 className="text-3xl font-bold tracking-tight">Ubah Produk</h1> <h1 className="text-3xl font-bold tracking-tight">Ubah Produk</h1>
</div> </div>
<Link href={productRoutes.index().url}>
<Button variant='outline'>
Kembali
</Button>
</Link>
</div> </div>
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm p-5"> <form onSubmit={onSubmit} className="space-y-6">
<CardContent className="p-0"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<form onSubmit={onSubmit} className="space-y-6 mt-4"> <div className="lg:col-span-2 space-y-6">
<FieldGroup> <Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
<CardHeader className="border-b">
<CardTitle>Informasi Produk</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
<Field> <Field>
<Label htmlFor="name">Nama</Label> <Label htmlFor="name">Nama</Label>
<Input <Input
@ -85,50 +94,15 @@ export default function ProductEdit({ product, categories }: { product: Product,
/> />
{errors.name && <p className="text-xs text-red-500">{errors.name}</p>} {errors.name && <p className="text-xs text-red-500">{errors.name}</p>}
</Field> </Field>
</CardContent>
</Card>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
<Field> <CardHeader className="border-b">
<Label>Kategori</Label> <CardTitle>Harga Produk</CardTitle>
<Combobox </CardHeader>
multiple <CardContent>
autoHighlight <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
items={filteredCategories}
value={categories.filter(c => data.category_ids.includes(c.id))}
onValueChange={(selected) => {
setData(
"category_ids",
selected.map((item: Category) => item.id)
)
}}
>
<ComboboxChips ref={anchor} className="w-full">
<ComboboxValue>
{(values: Category[]) => (
<>
{values.map((value) => (
<ComboboxChip key={value.id}>
{value.name}
</ComboboxChip>
))}
<ComboboxChipsInput placeholder='Pilih Kategori' />
</>
)}
</ComboboxValue>
</ComboboxChips>
<ComboboxContent anchor={anchor}>
<ComboboxEmpty>Data tidak ditemukan.</ComboboxEmpty>
<ComboboxList>
{(item: Category) => (
<ComboboxItem key={item.id} value={item}>
{item.name}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
{errors.category_ids && <p className="text-xs text-red-500">{errors.category_ids}</p>}
</Field>
<Field> <Field>
<Label htmlFor="price_purchase">Harga Beli</Label> <Label htmlFor="price_purchase">Harga Beli</Label>
<NumericFormat <NumericFormat
@ -209,7 +183,7 @@ export default function ProductEdit({ product, categories }: { product: Product,
{errors['prices.reseller'] && <p className="text-xs text-red-500">{errors['prices.reseller']}</p>} {errors['prices.reseller'] && <p className="text-xs text-red-500">{errors['prices.reseller']}</p>}
</Field> </Field>
<Field> <Field className="md:col-span-2">
<Label htmlFor="price_retail">Harga Retail</Label> <Label htmlFor="price_retail">Harga Retail</Label>
<NumericFormat <NumericFormat
id="price_retail" id="price_retail"
@ -229,28 +203,85 @@ export default function ProductEdit({ product, categories }: { product: Product,
{errors['prices.retail'] && <p className="text-xs text-red-500">{errors['prices.retail']}</p>} {errors['prices.retail'] && <p className="text-xs text-red-500">{errors['prices.retail']}</p>}
</Field> </Field>
</div> </div>
</CardContent>
</Card>
</div>
{/* Right Column - 1/3 width */}
<div className="space-y-6">
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
<CardHeader className="border-b">
<CardTitle>Kategori</CardTitle>
</CardHeader>
<CardContent>
<Field>
<Combobox
multiple
autoHighlight
items={filteredCategories}
value={categories.filter(c => data.category_ids.includes(c.id))}
onValueChange={(selected) => {
setData(
"category_ids",
selected.map((item: Category) => item.id)
)
}}
>
<ComboboxChips ref={anchor} className="w-full">
<ComboboxValue>
{(values: Category[]) => (
<>
{values.map((value) => (
<ComboboxChip key={value.id}>
{value.name}
</ComboboxChip>
))}
<ComboboxChipsInput placeholder='Pilih Kategori' />
</>
)}
</ComboboxValue>
</ComboboxChips>
<ComboboxContent anchor={anchor}>
<ComboboxEmpty>Data tidak ditemukan.</ComboboxEmpty>
<ComboboxList>
{(item: Category) => (
<ComboboxItem key={item.id} value={item}>
{item.name}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>
{errors.category_ids && <p className="text-xs text-red-500 mt-1">{errors.category_ids}</p>}
</Field>
</CardContent>
</Card>
<Card className="overflow-hidden border-none shadow-lg bg-card/50 backdrop-blur-sm">
<CardHeader className="border-b">
<CardTitle>Deskripsi</CardTitle>
</CardHeader>
<CardContent>
<Field> <Field>
<Label htmlFor="description">Deskripsi</Label>
<SimpleEditor <SimpleEditor
value={data.description} value={data.description}
onChange={(val) => setData('description', val)} onChange={(val) => setData('description', val)}
/> />
{errors.description && <p className="text-xs text-red-500">{errors.description}</p>} {errors.description && <p className="text-xs text-red-500 mt-1">{errors.description}</p>}
</Field> </Field>
</FieldGroup> </CardContent>
</Card>
<div className="flex gap-4 p-4 mt-6 rounded-lg justify-end"> <div className="flex flex-col gap-4 p-4 rounded-xl bg-primary/5 border border-primary/10 shadow-sm">
<Link href={productRoutes.index().url}> <div className="flex flex-col gap-2">
<Button type="button" variant="outline">Kembali</Button> <Button type="submit" className="w-full" disabled={processing}>
</Link>
<Button type="submit" disabled={processing}>
{processing ? 'Menyimpan...' : 'Simpan'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</div> </div>
</div>
</div>
</div>
</form> </form>
</CardContent>
</Card>
</div> </div>
); );