feat: enhance UI consistency by adding consistent icons to action buttons and loaders to save operations across admin modules

This commit is contained in:
Yoga Pangestu 2026-04-22 20:26:20 +07:00
parent 2b43213ee4
commit 26eaaf6d66
18 changed files with 209 additions and 86 deletions

View File

@ -2,7 +2,7 @@ import { Head } from '@inertiajs/react';
import type { Expense } from '@/types'; import type { Expense } from '@/types';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Trash2, X } from 'lucide-react'; import { Trash2, X, Plus } from 'lucide-react';
import { DataTable } from '@/components/data-table'; import { DataTable } from '@/components/data-table';
import { import {
AlertDialog, AlertDialog,
@ -55,7 +55,8 @@ export default function ExpenseIndex({ expenses }: { expenses: Expense[] }) {
<div> <div>
<h1 className="text-3xl font-bold tracking-tight">Pengeluaran</h1> <h1 className="text-3xl font-bold tracking-tight">Pengeluaran</h1>
</div> </div>
<Button onClick={onAdd}> <Button onClick={onAdd} className="gap-2">
<Plus className="size-4" />
Tambah Tambah
</Button> </Button>
</div> </div>
@ -119,8 +120,14 @@ export default function ExpenseIndex({ expenses }: { expenses: Expense[] }) {
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel> <AlertDialogCancel variant="outline" className="gap-2">
<AlertDialogAction onClick={confirmDelete} variant="destructive">Hapus</AlertDialogAction> <X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction onClick={confirmDelete} variant="destructive" className="gap-2">
<Trash2 className="size-4" />
Hapus
</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
@ -138,11 +145,16 @@ export default function ExpenseIndex({ expenses }: { expenses: Expense[] }) {
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel> <AlertDialogCancel variant="outline" className="gap-2">
<X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={confirmBulkDelete} onClick={confirmBulkDelete}
variant="destructive" variant="destructive"
className="gap-2"
> >
<Trash2 className="size-4" />
Hapus Hapus
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>

View File

@ -15,7 +15,7 @@ import { useEffect, useState, useRef } from 'react';
import expenseRoutes from '@/routes/expense'; import expenseRoutes from '@/routes/expense';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { NumericFormat } from 'react-number-format'; import { NumericFormat } from 'react-number-format';
import { ImagePlus, X } from 'lucide-react'; import { ImagePlus, X, Save, Loader } from 'lucide-react';
interface ExpenseFormModalProps { interface ExpenseFormModalProps {
isOpen: boolean; isOpen: boolean;
@ -183,8 +183,12 @@ export function ExpenseFormModal({ isOpen, onClose, expense }: ExpenseFormModalP
</Field> </Field>
</FieldGroup> </FieldGroup>
<DialogFooter className="mt-6"> <DialogFooter className="mt-6">
<Button type="button" variant="outline" onClick={handleClose}>Batal</Button> <Button type="button" variant="outline" className="gap-2" onClick={handleClose}>
<Button type="submit" disabled={processing}> <X className="size-4" />
Batal
</Button>
<Button type="submit" className="gap-2" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@ -2,7 +2,7 @@ import { Head } from '@inertiajs/react';
import type { Payroll } from '@/types'; import type { Payroll } from '@/types';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Trash2 } from 'lucide-react'; import { Trash2, Sparkles, Plus, X } from 'lucide-react';
import { DataTable } from '@/components/data-table'; import { DataTable } from '@/components/data-table';
import { import {
AlertDialog, AlertDialog,
@ -60,7 +60,8 @@ export default function PayrollIndex({
<div> <div>
<h1 className="text-3xl font-bold tracking-tight">Penggajian</h1> <h1 className="text-3xl font-bold tracking-tight">Penggajian</h1>
</div> </div>
<Button onClick={onGenerate} disabled={isGenerating}> <Button onClick={onGenerate} disabled={isGenerating} className="gap-2">
{isGenerating ? <Sparkles className="size-4 animate-spin" /> : <Sparkles className="size-4" />}
Generate Generate
</Button> </Button>
</div> </div>
@ -158,8 +159,14 @@ export default function PayrollIndex({
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel> <AlertDialogCancel variant="outline" className="gap-2">
<AlertDialogAction onClick={confirmDelete} variant="destructive">Hapus</AlertDialogAction> <X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction onClick={confirmDelete} variant="destructive" className="gap-2">
<Trash2 className="size-4" />
Hapus
</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
@ -177,11 +184,16 @@ export default function PayrollIndex({
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel> <AlertDialogCancel variant="outline" className="gap-2">
<X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={confirmBulkDelete} onClick={confirmBulkDelete}
variant="destructive" variant="destructive"
className="gap-2"
> >
<Trash2 className="size-4" />
Hapus Hapus
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>

View File

@ -16,10 +16,11 @@ import { useForm } from '@inertiajs/react';
import { useEffect } from 'react'; import { useEffect } from 'react';
import payrollRoutes from '@/routes/payroll'; import payrollRoutes from '@/routes/payroll';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Plus, Trash2 } from 'lucide-react'; import { Loader, Plus, Trash2 } from 'lucide-react';
import { NumericFormat } from 'react-number-format'; import { NumericFormat } from 'react-number-format';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from '@/components/ui/empty'; import { Empty, EmptyDescription, EmptyHeader, EmptyTitle } from '@/components/ui/empty';
import { X, Save } from 'lucide-react';
interface PayrollFormModalProps { interface PayrollFormModalProps {
isOpen: boolean; isOpen: boolean;
@ -96,7 +97,8 @@ export function PayrollFormModal({ isOpen, onClose, payroll }: PayrollFormModalP
<FieldGroup> <FieldGroup>
<div className="flex items-center justify-between border-b pb-2 mb-4"> <div className="flex items-center justify-between border-b pb-2 mb-4">
<h3 className="text-sm font-medium">Bonus & Potongan</h3> <h3 className="text-sm font-medium">Bonus & Potongan</h3>
<Button type="button" variant="outline" size="sm" onClick={addAdjustment}> <Button type="button" variant="outline" size="sm" onClick={addAdjustment} className="gap-2">
<Plus className="size-4" />
Tambah Tambah
</Button> </Button>
</div> </div>
@ -180,8 +182,12 @@ export function PayrollFormModal({ isOpen, onClose, payroll }: PayrollFormModalP
</div> </div>
<DialogFooter className="mt-6"> <DialogFooter className="mt-6">
<Button type="button" variant="outline" onClick={handleClose}>Batal</Button> <Button type="button" variant="outline" className="gap-2" onClick={handleClose}>
<Button type="submit" disabled={processing}> <X className="size-4" />
Batal
</Button>
<Button type="submit" className="gap-2" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@ -9,7 +9,7 @@ import React, { useMemo, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Product, ProductPrice } from '@/types'; import { Product, ProductPrice } from '@/types';
import { OrderItem } from '@/types/order'; import { OrderItem } from '@/types/order';
import { Plus, Trash2, CalendarIcon, Minus, ShoppingCart, Search, ImagePlus, X, Tag } from 'lucide-react'; import { Plus, Trash2, CalendarIcon, Minus, ShoppingCart, Search, ImagePlus, X, Tag, ArrowLeft, Check, Loader, Save } from 'lucide-react';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
@ -66,8 +66,8 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha
const { data, setData, post, processing, errors, transform } = useForm({ const { data, setData, post, processing, errors, transform } = useForm({
invoice_number: '', invoice_number: '',
customer_name: '', customer_name: '',
discount: '', discount: 0,
payment: '', payment: 0,
payment_method: 'cash', payment_method: 'cash',
order_status: 'delivered', order_status: 'delivered',
order_channel: 'store', order_channel: 'store',
@ -181,8 +181,8 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha
return cartItems.reduce((acc, item) => acc + item.total, 0); return cartItems.reduce((acc, item) => acc + item.total, 0);
}, [cartItems]); }, [cartItems]);
const total = subtotal - data.discount; const total = subtotal - (Number(data.discount) || 0);
const change = data.payment - total; const change = (Number(data.payment) || 0) - total;
const cartTotalItems = cartItems.reduce((a, i) => a + i.qty, 0); const cartTotalItems = cartItems.reduce((a, i) => a + i.qty, 0);
const PriceTypeLabel = ({ type }: { type: string }) => { const PriceTypeLabel = ({ type }: { type: string }) => {
@ -365,7 +365,7 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha
autoComplete='off' autoComplete='off'
/> />
</Field> </Field>
{change >= 0 && data.payment > 0 && ( {change >= 0 && (Number(data.payment) || 0) > 0 && (
<div className="flex justify-between items-center p-2 rounded bg-green-500/10 border border-green-500/20 text-green-600 dark:text-green-400"> <div className="flex justify-between items-center p-2 rounded bg-green-500/10 border border-green-500/20 text-green-600 dark:text-green-400">
<span className="text-xs font-bold uppercase">Kembalian</span> <span className="text-xs font-bold uppercase">Kembalian</span>
<span className="text-lg font-bold">Rp {change.toLocaleString('id-ID')}</span> <span className="text-lg font-bold">Rp {change.toLocaleString('id-ID')}</span>
@ -383,8 +383,9 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha
} }
}} }}
> >
<ShoppingCart className="h-4 w-4 mr-2" /> {processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save
{processing ? 'Menyimpan...' : 'Checkout Pesanan'} className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</div> </div>
</div> </div>
@ -397,7 +398,10 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha
<div className="flex items-center justify-between shrink-0"> <div className="flex items-center justify-between shrink-0">
<h1 className="text-3xl font-bold tracking-tight">Tambah Pesanan</h1> <h1 className="text-3xl font-bold tracking-tight">Tambah Pesanan</h1>
<Link href={orderRoutes.index().url}> <Link href={orderRoutes.index().url}>
<Button variant='outline'>Kembali</Button> <Button variant='outline' className="gap-2">
<ArrowLeft className="size-4" />
Kembali
</Button>
</Link> </Link>
</div> </div>
@ -616,8 +620,14 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha
/> />
</div> </div>
<DialogFooter> <DialogFooter>
<Button variant="outline" onClick={() => setQtyDialogIndex(null)}>Batal</Button> <Button variant="outline" className="gap-2" onClick={() => setQtyDialogIndex(null)}>
<Button onClick={confirmQty}>Konfirmasi</Button> <X className="size-4" />
Batal
</Button>
<Button className="gap-2" onClick={confirmQty}>
<Check className="size-4" />
Konfirmasi
</Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@ -9,7 +9,7 @@ import React, { useMemo, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Product, ProductPrice } from '@/types'; import { Product, ProductPrice } from '@/types';
import { Order, OrderItem } from '@/types/order'; import { Order, OrderItem } from '@/types/order';
import { Plus, Trash2, CalendarIcon, Minus, ShoppingCart, Search, ImagePlus, X, Tag } from 'lucide-react'; import { Plus, Trash2, CalendarIcon, Minus, ShoppingCart, Search, ImagePlus, X, Tag, ArrowLeft, Check, Save, Loader } from 'lucide-react';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
@ -371,7 +371,7 @@ export default function OrderEdit({ order, products, orderStatus, orderChannels,
<Button <Button
type="submit" type="submit"
className="w-full h-10 font-semibold shadow" className="w-full h-10 font-semibold shadow gap-2"
disabled={processing || data.items.length === 0} disabled={processing || data.items.length === 0}
onClick={() => { onClick={() => {
if (data.items.length > 0) { if (data.items.length > 0) {
@ -379,8 +379,8 @@ export default function OrderEdit({ order, products, orderStatus, orderChannels,
} }
}} }}
> >
<ShoppingCart className="h-4 w-4 mr-2" /> {processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Perbarui Pesanan'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</div> </div>
</div> </div>
@ -393,7 +393,10 @@ export default function OrderEdit({ order, products, orderStatus, orderChannels,
<div className="flex items-center justify-between shrink-0"> <div className="flex items-center justify-between shrink-0">
<h1 className="text-3xl font-bold tracking-tight">Ubah Pesanan</h1> <h1 className="text-3xl font-bold tracking-tight">Ubah Pesanan</h1>
<Link href={orderRoutes.index().url}> <Link href={orderRoutes.index().url}>
<Button variant='outline'>Kembali</Button> <Button variant='outline' className="gap-2">
<ArrowLeft className="size-4" />
Kembali
</Button>
</Link> </Link>
</div> </div>
@ -603,8 +606,14 @@ export default function OrderEdit({ order, products, orderStatus, orderChannels,
/> />
</div> </div>
<DialogFooter> <DialogFooter>
<Button variant="outline" onClick={() => setQtyDialogIndex(null)}>Batal</Button> <Button variant="outline" className="gap-2" onClick={() => setQtyDialogIndex(null)}>
<Button onClick={confirmQty}>Konfirmasi</Button> <X className="size-4" />
Batal
</Button>
<Button className="gap-2" onClick={confirmQty}>
<Check className="size-4" />
Konfirmasi
</Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@ -3,7 +3,7 @@ import { useCallback } from 'react';
import type { Order } from '@/types/order'; import type { Order } from '@/types/order';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Trash2, ShoppingBag, Printer, Bluetooth, Usb, Unplug } from 'lucide-react'; import { Trash2, ShoppingBag, Printer, Bluetooth, Usb, Unplug, Plus, X, ArrowLeft } from 'lucide-react';
import { DataTable } from '@/components/data-table'; import { DataTable } from '@/components/data-table';
import { import {
DropdownMenu, DropdownMenu,
@ -184,7 +184,8 @@ export default function OrderIndex({ orders }: { orders: Order[] }) {
</DropdownMenu> </DropdownMenu>
<Link href={orderRoutes.create().url}> <Link href={orderRoutes.create().url}>
<Button> <Button className="gap-2">
<Plus className="size-4" />
Tambah Tambah
</Button> </Button>
</Link> </Link>
@ -220,14 +221,20 @@ export default function OrderIndex({ orders }: { orders: Order[] }) {
<AlertDialogMedia className="bg-destructive/10 text-destructive dark:bg-destructive/20 dark:text-destructive"> <AlertDialogMedia className="bg-destructive/10 text-destructive dark:bg-destructive/20 dark:text-destructive">
<Trash2 className="size-5" /> <Trash2 className="size-5" />
</AlertDialogMedia> </AlertDialogMedia>
<AlertDialogTitle>Hapus data pesanan?</AlertDialogTitle> <AlertDialogTitle>Hapus pesanan?</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
Tindakan ini tidak dapat dibatalkan. Data pesanan dengan nomor invoice <strong>{orderToDelete?.invoice_number}</strong> akan dihapus secara permanen dan stok akan dikembalikan. Tindakan ini tidak dapat dibatalkan. Data pesanan dengan nomor invoice <strong>{orderToDelete?.invoice_number}</strong> akan dihapus secara permanen dan stok akan dikembalikan.
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel> <AlertDialogCancel variant="outline" className="gap-2">
<AlertDialogAction onClick={confirmDelete} variant="destructive">Hapus</AlertDialogAction> <X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction onClick={confirmDelete} variant="destructive" className="gap-2">
<Trash2 className="size-4" />
Hapus
</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
@ -245,11 +252,16 @@ export default function OrderIndex({ orders }: { orders: Order[] }) {
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel> <AlertDialogCancel variant="outline" className="gap-2">
<X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={confirmBulkDelete} onClick={confirmBulkDelete}
variant="destructive" variant="destructive"
className="gap-2"
> >
<Trash2 className="size-4" />
Hapus Hapus
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>

View File

@ -8,7 +8,7 @@ import purchaseRoutes from '@/routes/purchase';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Product, ProductPrice } from '@/types'; import { Product, ProductPrice } from '@/types';
import { Plus, Trash2, CalendarIcon, Minus, ShoppingCart, Search, ImagePlus, X } from 'lucide-react'; import { Plus, Trash2, CalendarIcon, Minus, ShoppingCart, Search, ImagePlus, X, Loader, Save } from 'lucide-react';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Calendar } from '@/components/ui/calendar'; import { Calendar } from '@/components/ui/calendar';
import { format } from 'date-fns'; import { format } from 'date-fns';
@ -300,8 +300,8 @@ export default function PurchaseCreate({ products, cartItems }: { products: Prod
} }
}} }}
> >
<ShoppingCart className="h-4 w-4 mr-2" /> {processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Checkout Belanja'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</div> </div>
</div> </div>
@ -593,8 +593,8 @@ export default function PurchaseCreate({ products, cartItems }: { products: Prod
className="w-full h-10 font-semibold shadow" className="w-full h-10 font-semibold shadow"
disabled={processing || cartItems.length === 0} disabled={processing || cartItems.length === 0}
> >
<ShoppingCart className="h-4 w-4 mr-2" /> {processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Checkout Belanja'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</div> </div>
</div> </div>
@ -769,8 +769,8 @@ export default function PurchaseCreate({ products, cartItems }: { products: Prod
disabled={processing || cartItems.length === 0} disabled={processing || cartItems.length === 0}
onClick={() => onSubmit({ preventDefault: () => { } } as any)} onClick={() => onSubmit({ preventDefault: () => { } } as any)}
> >
<ShoppingCart className="h-4 w-4 mr-2" /> {processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Checkout Belanja'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</div> </div>
</SheetContent> </SheetContent>

View File

@ -8,7 +8,7 @@ import purchaseRoutes from '@/routes/purchase';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Product, ProductPrice, Purchase } from '@/types'; import { Product, ProductPrice, Purchase } from '@/types';
import { Plus, Trash2, CalendarIcon, Minus, ShoppingCart, Search, ImagePlus, X } from 'lucide-react'; import { Plus, Trash2, CalendarIcon, Minus, ShoppingCart, Search, ImagePlus, X, Loader, Save } from 'lucide-react';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Calendar } from '@/components/ui/calendar'; import { Calendar } from '@/components/ui/calendar';
import { format } from 'date-fns'; import { format } from 'date-fns';
@ -304,8 +304,8 @@ export default function PurchaseEdit({ purchase, products }: { purchase: Purchas
} }
}} }}
> >
<ShoppingCart className="h-4 w-4 mr-2" /> {processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Perbarui Belanja'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</div> </div>
</div> </div>
@ -597,8 +597,8 @@ export default function PurchaseEdit({ purchase, products }: { purchase: Purchas
className="w-full h-10 font-semibold shadow" className="w-full h-10 font-semibold shadow"
disabled={processing || data.items.length === 0} disabled={processing || data.items.length === 0}
> >
<ShoppingCart className="h-4 w-4 mr-2" /> {processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Perbarui Belanja'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</div> </div>
</div> </div>
@ -773,8 +773,8 @@ export default function PurchaseEdit({ purchase, products }: { purchase: Purchas
disabled={processing || data.items.length === 0} disabled={processing || data.items.length === 0}
onClick={() => onSubmit({ preventDefault: () => { } } as any)} onClick={() => onSubmit({ preventDefault: () => { } } as any)}
> >
<ShoppingCart className="h-4 w-4 mr-2" /> {processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Perbarui Belanja'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</div> </div>
</SheetContent> </SheetContent>

View File

@ -2,7 +2,7 @@ import { Head } from '@inertiajs/react';
import type { Category } from '@/types'; import type { Category } from '@/types';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Trash2 } from 'lucide-react'; import { Trash2, Plus, X } from 'lucide-react';
import { DataTable } from '@/components/data-table'; import { DataTable } from '@/components/data-table';
import { import {
AlertDialog, AlertDialog,
@ -52,7 +52,8 @@ export default function CategoryIndex({ categories }: { categories: Category[] }
<div> <div>
<h1 className="text-3xl font-bold tracking-tight">Kategori</h1> <h1 className="text-3xl font-bold tracking-tight">Kategori</h1>
</div> </div>
<Button onClick={onAdd}> <Button onClick={onAdd} className="gap-2">
<Plus className="size-4" />
Tambah Tambah
</Button> </Button>
</div> </div>
@ -108,8 +109,14 @@ export default function CategoryIndex({ categories }: { categories: Category[] }
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel> <AlertDialogCancel variant="outline" className="gap-2">
<AlertDialogAction onClick={confirmDelete} variant="destructive">Hapus</AlertDialogAction> <X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction onClick={confirmDelete} variant="destructive" className="gap-2">
<Trash2 className="size-4" />
Hapus
</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
@ -127,11 +134,16 @@ export default function CategoryIndex({ categories }: { categories: Category[] }
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel> <AlertDialogCancel variant="outline" className="gap-2">
<X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={confirmBulkDelete} onClick={confirmBulkDelete}
variant="destructive" variant="destructive"
className="gap-2"
> >
<Trash2 className="size-4" />
Hapus Hapus
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>

View File

@ -14,6 +14,7 @@ import { useForm } from '@inertiajs/react';
import { useEffect } from 'react'; import { useEffect } from 'react';
import categoryRoutes from '@/routes/category'; import categoryRoutes from '@/routes/category';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { X, Save, Loader } from 'lucide-react';
interface CategoryFormModalProps { interface CategoryFormModalProps {
isOpen: boolean; isOpen: boolean;
@ -88,8 +89,12 @@ export function CategoryFormModal({ isOpen, onClose, category }: CategoryFormMod
</Field> </Field>
</FieldGroup> </FieldGroup>
<DialogFooter className="mt-6"> <DialogFooter className="mt-6">
<Button type="button" variant="outline" onClick={handleClose}>Batal</Button> <Button type="button" variant="outline" className="gap-2" onClick={handleClose}>
<Button type="submit" disabled={processing}> <X className="size-4" />
Batal
</Button>
<Button type="submit" className="gap-2" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@ -23,7 +23,7 @@ import {
useComboboxAnchor, useComboboxAnchor,
} from "@/components/ui/combobox" } from "@/components/ui/combobox"
import { NumericFormat } from 'react-number-format'; import { NumericFormat } from 'react-number-format';
import { ImagePlus, X, Upload } from 'lucide-react'; import { ImagePlus, X, Upload, ArrowLeft, Save, Loader } from 'lucide-react';
export default function ProductCreate({ categories }: { categories: Category[] }) { export default function ProductCreate({ categories }: { categories: Category[] }) {
const { data, setData, post, processing, errors } = useForm<{ const { data, setData, post, processing, errors } = useForm<{
@ -117,7 +117,8 @@ export default function ProductCreate({ categories }: { categories: Category[] }
<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}> <Link href={productRoutes.index().url}>
<Button variant='outline'> <Button variant='outline' className="gap-2">
<ArrowLeft className="size-4" />
Kembali Kembali
</Button> </Button>
</Link> </Link>
@ -401,7 +402,8 @@ export default function ProductCreate({ categories }: { categories: Category[] }
</Card> </Card>
<div className="flex flex-col gap-4 p-4 rounded-xl bg-primary/5 border border-primary/10 shadow-sm"> <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" disabled={processing}> <Button type="submit" className="w-full gap-2" disabled={processing}>
{processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Simpan'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</div> </div>

View File

@ -23,7 +23,7 @@ import {
useComboboxAnchor, useComboboxAnchor,
} from "@/components/ui/combobox" } from "@/components/ui/combobox"
import { NumericFormat } from 'react-number-format'; import { NumericFormat } from 'react-number-format';
import { ImagePlus, X, Upload } from 'lucide-react'; import { ImagePlus, X, Upload, ArrowLeft, Save, Loader } from 'lucide-react';
interface ExistingImage { interface ExistingImage {
kind: 'existing'; kind: 'existing';
@ -167,7 +167,8 @@ export default function ProductEdit({ product, categories }: { product: Product,
<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}> <Link href={productRoutes.index().url}>
<Button variant='outline'> <Button variant='outline' className="gap-2">
<ArrowLeft className="size-4" />
Kembali Kembali
</Button> </Button>
</Link> </Link>
@ -469,7 +470,8 @@ export default function ProductEdit({ product, categories }: { product: Product,
<div className="flex flex-col gap-4 p-4 rounded-xl bg-primary/5 border border-primary/10 shadow-sm"> <div className="flex flex-col gap-4 p-4 rounded-xl bg-primary/5 border border-primary/10 shadow-sm">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<Button type="submit" className="w-full" disabled={processing}> <Button type="submit" className="w-full gap-2" disabled={processing}>
{processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Simpan'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</div> </div>

View File

@ -2,7 +2,7 @@ import { Head, Link } from '@inertiajs/react';
import type { Product, Category } from '@/types'; import type { Product, Category } from '@/types';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Trash2 } from 'lucide-react'; import { Trash2, Plus, X } from 'lucide-react';
import { DataTable } from '@/components/data-table'; import { DataTable } from '@/components/data-table';
import { import {
AlertDialog, AlertDialog,
@ -52,7 +52,8 @@ export default function ProductIndex({ products, categories }: { products: Produ
<h1 className="text-3xl font-bold tracking-tight">Produk</h1> <h1 className="text-3xl font-bold tracking-tight">Produk</h1>
</div> </div>
<Link href={productRoutes.create().url}> <Link href={productRoutes.create().url}>
<Button> <Button className="gap-2">
<Plus className="size-4" />
Tambah Tambah
</Button> </Button>
</Link> </Link>
@ -111,8 +112,14 @@ export default function ProductIndex({ products, categories }: { products: Produ
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel> <AlertDialogCancel variant="outline" className="gap-2">
<AlertDialogAction onClick={confirmDelete} variant="destructive">Hapus</AlertDialogAction> <X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction onClick={confirmDelete} variant="destructive" className="gap-2">
<Trash2 className="size-4" />
Hapus
</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
@ -130,11 +137,16 @@ export default function ProductIndex({ products, categories }: { products: Produ
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel> <AlertDialogCancel variant="outline" className="gap-2">
<X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={confirmBulkDelete} onClick={confirmBulkDelete}
variant="destructive" variant="destructive"
className="gap-2"
> >
<Trash2 className="size-4" />
Hapus Hapus
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>

View File

@ -16,6 +16,7 @@ import {
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover" } from "@/components/ui/popover"
import { NumericFormat } from 'react-number-format'; import { NumericFormat } from 'react-number-format';
import { ArrowLeft, Loader, Save } from 'lucide-react';
export default function UserCreate() { export default function UserCreate() {
const [isCalendarOpen, setIsCalendarOpen] = React.useState(false); const [isCalendarOpen, setIsCalendarOpen] = React.useState(false);
@ -50,7 +51,8 @@ export default function UserCreate() {
<h1 className="text-3xl font-bold tracking-tight">Tambah Pegawai</h1> <h1 className="text-3xl font-bold tracking-tight">Tambah Pegawai</h1>
</div> </div>
<Link href={userRoutes.index().url}> <Link href={userRoutes.index().url}>
<Button variant='outline'> <Button variant='outline' className="gap-2">
<ArrowLeft className="size-4" />
Kembali Kembali
</Button> </Button>
</Link> </Link>
@ -223,6 +225,7 @@ export default function UserCreate() {
<div className="flex flex-col gap-4 p-4 rounded-xl bg-primary/5 border border-primary/10 shadow-sm"> <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" disabled={processing}> <Button type="submit" className="w-full" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</div> </div>

View File

@ -16,6 +16,7 @@ import {
PopoverTrigger, PopoverTrigger,
} from "@/components/ui/popover" } from "@/components/ui/popover"
import { NumericFormat } from 'react-number-format'; import { NumericFormat } from 'react-number-format';
import { ArrowLeft, Loader, Save } from 'lucide-react';
export default function UserEdit({ user }: { user: any }) { export default function UserEdit({ user }: { user: any }) {
const [isCalendarOpen, setIsCalendarOpen] = React.useState(false); const [isCalendarOpen, setIsCalendarOpen] = React.useState(false);
@ -52,6 +53,7 @@ export default function UserEdit({ user }: { user: any }) {
</div> </div>
<Link href={userRoutes.index().url}> <Link href={userRoutes.index().url}>
<Button variant='outline'> <Button variant='outline'>
<ArrowLeft className="size-4" />
Kembali Kembali
</Button> </Button>
</Link> </Link>
@ -224,6 +226,7 @@ export default function UserEdit({ user }: { user: any }) {
<div className="flex flex-col gap-4 p-4 rounded-xl bg-primary/5 border border-primary/10 shadow-sm"> <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" disabled={processing}> <Button type="submit" className="w-full" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'} {processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</div> </div>

View File

@ -2,7 +2,7 @@ import { Head, Link } from '@inertiajs/react';
import type { User } from '@/types'; import type { User } from '@/types';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Trash2, KeyRound } from 'lucide-react'; import { Trash2, KeyRound, Plus, X } from 'lucide-react';
import { DataTable } from '@/components/data-table'; import { DataTable } from '@/components/data-table';
import { import {
AlertDialog, AlertDialog,
@ -52,7 +52,8 @@ export default function UserIndex({ users, defaultPassword }: { users: User[], d
<h1 className="text-3xl font-bold tracking-tight">Pegawai</h1> <h1 className="text-3xl font-bold tracking-tight">Pegawai</h1>
</div> </div>
<Link href={userRoutes.create().url}> <Link href={userRoutes.create().url}>
<Button> <Button className="gap-2">
<Plus className="size-4" />
Tambah Tambah
</Button> </Button>
</Link> </Link>
@ -93,8 +94,14 @@ export default function UserIndex({ users, defaultPassword }: { users: User[], d
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel> <AlertDialogCancel variant="outline" className="gap-2">
<AlertDialogAction onClick={confirmDelete} variant="destructive">Hapus</AlertDialogAction> <X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction onClick={confirmDelete} variant="destructive" className="gap-2">
<Trash2 className="size-4" />
Hapus
</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
@ -106,14 +113,20 @@ export default function UserIndex({ users, defaultPassword }: { users: User[], d
<AlertDialogMedia className="bg-blue-100 text-blue-600 dark:bg-blue-900/20 dark:text-blue-500"> <AlertDialogMedia className="bg-blue-100 text-blue-600 dark:bg-blue-900/20 dark:text-blue-500">
<KeyRound className="size-5" /> <KeyRound className="size-5" />
</AlertDialogMedia> </AlertDialogMedia>
<AlertDialogTitle>Reset password?</AlertDialogTitle> <AlertDialogTitle>Reset kata sandi?</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
Tindakan ini akan mereset password <strong>{userToReset?.name}</strong> menjadi <strong>{defaultPassword}</strong>. Pengguna dapat mengubahnya kembali nanti. Tindakan ini akan mereset kata sandi <strong>{userToReset?.name}</strong> menjadi <strong>{defaultPassword}</strong>. Pengguna dapat mengubahnya kembali nanti.
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel> <AlertDialogCancel variant="outline" className="gap-2">
<AlertDialogAction onClick={confirmResetPassword} className="bg-blue-600 hover:bg-blue-700 text-white focus:ring-blue-600">Reset</AlertDialogAction> <X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction onClick={confirmResetPassword} className="bg-blue-600 hover:bg-blue-700 text-white focus:ring-blue-600 gap-2">
<KeyRound className="size-4" />
Reset
</AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
@ -131,11 +144,16 @@ export default function UserIndex({ users, defaultPassword }: { users: User[], d
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel> <AlertDialogCancel variant="outline" className="gap-2">
<X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction <AlertDialogAction
onClick={confirmBulkDelete} onClick={confirmBulkDelete}
variant="destructive" variant="destructive"
className="gap-2"
> >
<Trash2 className="size-4" />
Hapus Hapus
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>

View File

@ -9,7 +9,7 @@ import type { GeneralSetting } from '@/types';
import React, { useRef, useState } from 'react'; import React, { useRef, useState } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import systemRoutes from '@/routes/system'; import systemRoutes from '@/routes/system';
import { ImagePlus, X } from 'lucide-react'; import { ImagePlus, X, Save, Loader } from 'lucide-react';
export default function SettingIndex({ setting }: { setting: GeneralSetting | null }) { export default function SettingIndex({ setting }: { setting: GeneralSetting | null }) {
const { data, setData, post, processing, errors } = useForm({ const { data, setData, post, processing, errors } = useForm({
@ -227,8 +227,9 @@ export default function SettingIndex({ setting }: { setting: GeneralSetting | nu
</Card> </Card>
<div className="flex flex-col gap-4 p-4 rounded-xl bg-primary/5 border border-primary/10 shadow-sm"> <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" disabled={processing}> <Button type="submit" className="w-full gap-2" disabled={processing}>
{processing ? 'Menyimpan...' : 'Simpan Perubahan'} {processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button> </Button>
</div> </div>
</div> </div>