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

View File

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

View File

@ -2,7 +2,7 @@ import { Head } from '@inertiajs/react';
import type { Payroll } from '@/types';
import { Card, CardContent } from '@/components/ui/card';
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 {
AlertDialog,
@ -60,7 +60,8 @@ export default function PayrollIndex({
<div>
<h1 className="text-3xl font-bold tracking-tight">Penggajian</h1>
</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
</Button>
</div>
@ -158,8 +159,14 @@ export default function PayrollIndex({
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel>
<AlertDialogAction onClick={confirmDelete} variant="destructive">Hapus</AlertDialogAction>
<AlertDialogCancel variant="outline" className="gap-2">
<X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction onClick={confirmDelete} variant="destructive" className="gap-2">
<Trash2 className="size-4" />
Hapus
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
@ -177,11 +184,16 @@ export default function PayrollIndex({
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel>
<AlertDialogCancel variant="outline" className="gap-2">
<X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction
onClick={confirmBulkDelete}
variant="destructive"
className="gap-2"
>
<Trash2 className="size-4" />
Hapus
</AlertDialogAction>
</AlertDialogFooter>

View File

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

View File

@ -9,7 +9,7 @@ import React, { useMemo, useState } from 'react';
import { toast } from 'sonner';
import { Product, ProductPrice } from '@/types';
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 { format } from 'date-fns';
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({
invoice_number: '',
customer_name: '',
discount: '',
payment: '',
discount: 0,
payment: 0,
payment_method: 'cash',
order_status: 'delivered',
order_channel: 'store',
@ -181,8 +181,8 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha
return cartItems.reduce((acc, item) => acc + item.total, 0);
}, [cartItems]);
const total = subtotal - data.discount;
const change = data.payment - total;
const total = subtotal - (Number(data.discount) || 0);
const change = (Number(data.payment) || 0) - total;
const cartTotalItems = cartItems.reduce((a, i) => a + i.qty, 0);
const PriceTypeLabel = ({ type }: { type: string }) => {
@ -365,7 +365,7 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha
autoComplete='off'
/>
</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">
<span className="text-xs font-bold uppercase">Kembalian</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 ? 'Menyimpan...' : 'Checkout Pesanan'}
{processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save
className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</div>
@ -397,7 +398,10 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha
<div className="flex items-center justify-between shrink-0">
<h1 className="text-3xl font-bold tracking-tight">Tambah Pesanan</h1>
<Link href={orderRoutes.index().url}>
<Button variant='outline'>Kembali</Button>
<Button variant='outline' className="gap-2">
<ArrowLeft className="size-4" />
Kembali
</Button>
</Link>
</div>
@ -616,8 +620,14 @@ export default function OrderCreate({ products, cartItems, orderStatus, orderCha
/>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setQtyDialogIndex(null)}>Batal</Button>
<Button onClick={confirmQty}>Konfirmasi</Button>
<Button variant="outline" className="gap-2" onClick={() => setQtyDialogIndex(null)}>
<X className="size-4" />
Batal
</Button>
<Button className="gap-2" onClick={confirmQty}>
<Check className="size-4" />
Konfirmasi
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@ -9,7 +9,7 @@ import React, { useMemo, useState } from 'react';
import { toast } from 'sonner';
import { Product, ProductPrice } from '@/types';
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 { format } from 'date-fns';
import { cn } from '@/lib/utils';
@ -371,7 +371,7 @@ export default function OrderEdit({ order, products, orderStatus, orderChannels,
<Button
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}
onClick={() => {
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 ? 'Menyimpan...' : 'Perbarui Pesanan'}
{processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</div>
@ -393,7 +393,10 @@ export default function OrderEdit({ order, products, orderStatus, orderChannels,
<div className="flex items-center justify-between shrink-0">
<h1 className="text-3xl font-bold tracking-tight">Ubah Pesanan</h1>
<Link href={orderRoutes.index().url}>
<Button variant='outline'>Kembali</Button>
<Button variant='outline' className="gap-2">
<ArrowLeft className="size-4" />
Kembali
</Button>
</Link>
</div>
@ -603,8 +606,14 @@ export default function OrderEdit({ order, products, orderStatus, orderChannels,
/>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setQtyDialogIndex(null)}>Batal</Button>
<Button onClick={confirmQty}>Konfirmasi</Button>
<Button variant="outline" className="gap-2" onClick={() => setQtyDialogIndex(null)}>
<X className="size-4" />
Batal
</Button>
<Button className="gap-2" onClick={confirmQty}>
<Check className="size-4" />
Konfirmasi
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

View File

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

View File

@ -8,7 +8,7 @@ import purchaseRoutes from '@/routes/purchase';
import React, { useMemo, useState } from 'react';
import { toast } from 'sonner';
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 { Calendar } from '@/components/ui/calendar';
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 ? 'Menyimpan...' : 'Checkout Belanja'}
{processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</div>
@ -593,8 +593,8 @@ export default function PurchaseCreate({ products, cartItems }: { products: Prod
className="w-full h-10 font-semibold shadow"
disabled={processing || cartItems.length === 0}
>
<ShoppingCart className="h-4 w-4 mr-2" />
{processing ? 'Menyimpan...' : 'Checkout Belanja'}
{processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</div>
@ -769,8 +769,8 @@ export default function PurchaseCreate({ products, cartItems }: { products: Prod
disabled={processing || cartItems.length === 0}
onClick={() => onSubmit({ preventDefault: () => { } } as any)}
>
<ShoppingCart className="h-4 w-4 mr-2" />
{processing ? 'Menyimpan...' : 'Checkout Belanja'}
{processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</SheetContent>

View File

@ -8,7 +8,7 @@ import purchaseRoutes from '@/routes/purchase';
import React, { useMemo, useState } from 'react';
import { toast } from 'sonner';
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 { Calendar } from '@/components/ui/calendar';
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 ? 'Menyimpan...' : 'Perbarui Belanja'}
{processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</div>
@ -597,8 +597,8 @@ export default function PurchaseEdit({ purchase, products }: { purchase: Purchas
className="w-full h-10 font-semibold shadow"
disabled={processing || data.items.length === 0}
>
<ShoppingCart className="h-4 w-4 mr-2" />
{processing ? 'Menyimpan...' : 'Perbarui Belanja'}
{processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</div>
@ -773,8 +773,8 @@ export default function PurchaseEdit({ purchase, products }: { purchase: Purchas
disabled={processing || data.items.length === 0}
onClick={() => onSubmit({ preventDefault: () => { } } as any)}
>
<ShoppingCart className="h-4 w-4 mr-2" />
{processing ? 'Menyimpan...' : 'Perbarui Belanja'}
{processing ? <Loader className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>
</SheetContent>

View File

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

View File

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

View File

@ -23,7 +23,7 @@ import {
useComboboxAnchor,
} from "@/components/ui/combobox"
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[] }) {
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>
</div>
<Link href={productRoutes.index().url}>
<Button variant='outline'>
<Button variant='outline' className="gap-2">
<ArrowLeft className="size-4" />
Kembali
</Button>
</Link>
@ -401,7 +402,8 @@ export default function ProductCreate({ categories }: { categories: Category[] }
</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" 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'}
</Button>
</div>

View File

@ -23,7 +23,7 @@ import {
useComboboxAnchor,
} from "@/components/ui/combobox"
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 {
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>
</div>
<Link href={productRoutes.index().url}>
<Button variant='outline'>
<Button variant='outline' className="gap-2">
<ArrowLeft className="size-4" />
Kembali
</Button>
</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-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'}
</Button>
</div>

View File

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

View File

@ -16,6 +16,7 @@ import {
PopoverTrigger,
} from "@/components/ui/popover"
import { NumericFormat } from 'react-number-format';
import { ArrowLeft, Loader, Save } from 'lucide-react';
export default function UserCreate() {
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>
</div>
<Link href={userRoutes.index().url}>
<Button variant='outline'>
<Button variant='outline' className="gap-2">
<ArrowLeft className="size-4" />
Kembali
</Button>
</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">
<Button type="submit" className="w-full" disabled={processing}>
{processing ? <Loader className="size-4 animate-spin" /> : <Save className="size-4" />}
{processing ? 'Menyimpan...' : 'Simpan'}
</Button>
</div>

View File

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

View File

@ -2,7 +2,7 @@ import { Head, Link } from '@inertiajs/react';
import type { User } from '@/types';
import { Card, CardContent } from '@/components/ui/card';
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 {
AlertDialog,
@ -52,7 +52,8 @@ export default function UserIndex({ users, defaultPassword }: { users: User[], d
<h1 className="text-3xl font-bold tracking-tight">Pegawai</h1>
</div>
<Link href={userRoutes.create().url}>
<Button>
<Button className="gap-2">
<Plus className="size-4" />
Tambah
</Button>
</Link>
@ -93,8 +94,14 @@ export default function UserIndex({ users, defaultPassword }: { users: User[], d
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel>
<AlertDialogAction onClick={confirmDelete} variant="destructive">Hapus</AlertDialogAction>
<AlertDialogCancel variant="outline" className="gap-2">
<X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction onClick={confirmDelete} variant="destructive" className="gap-2">
<Trash2 className="size-4" />
Hapus
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</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">
<KeyRound className="size-5" />
</AlertDialogMedia>
<AlertDialogTitle>Reset password?</AlertDialogTitle>
<AlertDialogTitle>Reset kata sandi?</AlertDialogTitle>
<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>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel>
<AlertDialogAction onClick={confirmResetPassword} className="bg-blue-600 hover:bg-blue-700 text-white focus:ring-blue-600">Reset</AlertDialogAction>
<AlertDialogCancel variant="outline" className="gap-2">
<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>
</AlertDialogContent>
</AlertDialog>
@ -131,11 +144,16 @@ export default function UserIndex({ users, defaultPassword }: { users: User[], d
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel variant="outline">Batal</AlertDialogCancel>
<AlertDialogCancel variant="outline" className="gap-2">
<X className="size-4" />
Batal
</AlertDialogCancel>
<AlertDialogAction
onClick={confirmBulkDelete}
variant="destructive"
className="gap-2"
>
<Trash2 className="size-4" />
Hapus
</AlertDialogAction>
</AlertDialogFooter>

View File

@ -9,7 +9,7 @@ import type { GeneralSetting } from '@/types';
import React, { useRef, useState } from 'react';
import { toast } from 'sonner';
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 }) {
const { data, setData, post, processing, errors } = useForm({
@ -227,8 +227,9 @@ export default function SettingIndex({ setting }: { setting: GeneralSetting | nu
</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" disabled={processing}>
{processing ? 'Menyimpan...' : 'Simpan Perubahan'}
<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>