Delete and Edit Vendor

This commit is contained in:
efrilm 2025-09-12 15:23:19 +07:00
parent c91be1812b
commit 40c417ec72
5 changed files with 237 additions and 112 deletions

View File

@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { api } from '../api' import { api } from '../api'
import { Vendors } from '@/types/services/vendor' import { Vendor, Vendors } from '@/types/services/vendor'
interface VendorQueryParams { interface VendorQueryParams {
page?: number page?: number
@ -34,3 +34,13 @@ export function useVendors(params: VendorQueryParams = {}) {
} }
}) })
} }
export function useVendorById(id: string) {
return useQuery<Vendor>({
queryKey: ['vendors', id],
queryFn: async () => {
const res = await api.get(`/vendors/${id}`)
return res.data.data
}
})
}

View File

@ -1,20 +1,28 @@
'use client'
// MUI Imports // MUI Imports
import Card from '@mui/material/Card' import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent' import CardContent from '@mui/material/CardContent'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import Chip from '@mui/material/Chip' import Chip from '@mui/material/Chip'
import Divider from '@mui/material/Divider' import Divider from '@mui/material/Divider'
import Button from '@mui/material/Button'
import type { ButtonProps } from '@mui/material/Button'
// Type Imports
import type { ThemeColor } from '@core/types'
// Component Imports // Component Imports
import EditUserInfo from '@components/dialogs/edit-user-info'
import ConfirmationDialog from '@components/dialogs/confirmation-dialog'
import OpenDialogOnElementClick from '@components/dialogs/OpenDialogOnElementClick'
import CustomAvatar from '@core/components/mui/Avatar' import CustomAvatar from '@core/components/mui/Avatar'
import { useParams } from 'next/navigation'
import { useVendorById } from '@/services/queries/vendor'
import Loading from '@/components/layout/shared/Loading'
import { getInitials } from '@/utils/getInitials'
import OpenDialogOnElementClick from '@/components/dialogs/OpenDialogOnElementClick'
import { Box, Button, ButtonProps, CircularProgress } from '@mui/material'
import ConfirmationDialog from '@/components/dialogs/confirmation-dialog'
import EditUserInfo from '@/components/dialogs/edit-user-info'
import { ThemeColor } from '@/@core/types'
import { useState } from 'react'
import AddVendorDrawer from '../../list/AddVendorDrawer'
import ConfirmDeleteDialog from '@/components/dialogs/confirm-delete'
import { useRouter } from 'next/router'
import { useVendorsMutation } from '@/services/mutations/vendor'
// Vars // Vars
const userData = { const userData = {
@ -33,7 +41,25 @@ const userData = {
} }
const VendorDetails = () => { const VendorDetails = () => {
// Vars const [editVendorOpen, setEditVendorOpen] = useState(false)
const [openConfirm, setOpenConfirm] = useState(false)
const params = useParams()
const id = params?.id ?? ''
const { data: vendor, isLoading, error } = useVendorById(id as string)
const { deleteVendor } = useVendorsMutation()
const handleDelete = () => {
deleteVendor.mutate(id as string, {
onSuccess: () => {
setOpenConfirm(false)
window.history.back()
}
})
}
const buttonProps = (children: string, color: ThemeColor, variant: ButtonProps['variant']): ButtonProps => ({ const buttonProps = (children: string, color: ThemeColor, variant: ButtonProps['variant']): ButtonProps => ({
children, children,
color, color,
@ -42,13 +68,31 @@ const VendorDetails = () => {
return ( return (
<> <>
{isLoading ? (
<Box
position='absolute'
top={0}
left={0}
right={0}
bottom={0}
display='flex'
alignItems='center'
justifyContent='center'
bgcolor='rgba(255,255,255,0.7)'
zIndex={1}
>
<CircularProgress size={24} />
</Box>
) : (
<Card> <Card>
<CardContent className='flex flex-col pbs-12 gap-6'> <CardContent className='flex flex-col pbs-12 gap-6'>
<div className='flex flex-col gap-6'> <div className='flex flex-col gap-6'>
<div className='flex items-center justify-center flex-col gap-4'> <div className='flex items-center justify-center flex-col gap-4'>
<div className='flex flex-col items-center gap-4'> <div className='flex flex-col items-center gap-4'>
<CustomAvatar alt='user-profile' src='/images/avatars/1.png' variant='rounded' size={120} /> {/* <CustomAvatar alt='vendor-profile' variant='rounded' size={120}>
<Typography variant='h5'>{`${userData.firstName} ${userData.lastName}`}</Typography> {getInitials(vendor?.name as string)}
</CustomAvatar> */}
<Typography variant='h5'>{vendor?.name}</Typography>
</div> </div>
<Chip label='Vendor' color='primary' size='small' variant='tonal' /> <Chip label='Vendor' color='primary' size='small' variant='tonal' />
</div> </div>
@ -61,22 +105,22 @@ const VendorDetails = () => {
<div className='flex flex-col gap-2'> <div className='flex flex-col gap-2'>
<div className='flex items-center flex-wrap gap-x-1.5'> <div className='flex items-center flex-wrap gap-x-1.5'>
<Typography className='font-medium' color='text.primary'> <Typography className='font-medium' color='text.primary'>
Nama: Contact Person:
</Typography> </Typography>
<Typography>{`${userData.firstName} ${userData.lastName}`}</Typography> <Typography>{vendor?.contact_person}</Typography>
</div> </div>
<div className='flex items-center flex-wrap gap-x-1.5'> <div className='flex items-center flex-wrap gap-x-1.5'>
<Typography className='font-medium' color='text.primary'> <Typography className='font-medium' color='text.primary'>
Perusahaan: Perusahaan:
</Typography> </Typography>
<Typography>{userData.perusahaan}</Typography> <Typography>{vendor?.name}</Typography>
</div> </div>
<div className='flex items-center flex-wrap gap-x-1.5'> <div className='flex items-center flex-wrap gap-x-1.5'>
<Typography className='font-medium' color='text.primary'> <Typography className='font-medium' color='text.primary'>
Email: Email:
</Typography> </Typography>
<Typography color='primary' sx={{ textDecoration: 'none', cursor: 'pointer' }}> <Typography color='primary' sx={{ textDecoration: 'none', cursor: 'pointer' }}>
{userData.email} {vendor?.email}
</Typography> </Typography>
</div> </div>
<div className='flex items-center flex-wrap gap-x-1.5'> <div className='flex items-center flex-wrap gap-x-1.5'>
@ -84,7 +128,7 @@ const VendorDetails = () => {
Telepon: Telepon:
</Typography> </Typography>
<Typography color='primary' sx={{ textDecoration: 'none', cursor: 'pointer' }}> <Typography color='primary' sx={{ textDecoration: 'none', cursor: 'pointer' }}>
{userData.telepon} {vendor?.phone_number}
</Typography> </Typography>
</div> </div>
<div className='flex items-center flex-wrap gap-x-1.5'> <div className='flex items-center flex-wrap gap-x-1.5'>
@ -92,7 +136,7 @@ const VendorDetails = () => {
Alamat Penagihan: Alamat Penagihan:
</Typography> </Typography>
<Typography color='primary' sx={{ textDecoration: 'none', cursor: 'pointer' }}> <Typography color='primary' sx={{ textDecoration: 'none', cursor: 'pointer' }}>
{userData.alamatPenagihan} {vendor?.address ?? '-'}
</Typography> </Typography>
</div> </div>
</div> </div>
@ -125,8 +169,31 @@ const VendorDetails = () => {
</div> </div>
</div> </div>
</div> </div>
<div className='flex gap-4 justify-center'>
<Button variant='contained' onClick={() => setEditVendorOpen(!editVendorOpen)} className='max-sm:is-full'>
Edit
</Button>
<Button
variant='contained'
color='error'
onClick={() => setOpenConfirm(!openConfirm)}
className='max-sm:is-full'
>
Hapus
</Button>
</div>
</CardContent> </CardContent>
</Card> </Card>
)}
<AddVendorDrawer open={editVendorOpen} handleClose={() => setEditVendorOpen(!editVendorOpen)} data={vendor} />
<ConfirmDeleteDialog
open={openConfirm}
onClose={() => setOpenConfirm(false)}
onConfirm={handleDelete}
isLoading={deleteVendor.isPending}
title='Delete Vendor'
message='Are you sure you want to delete this Vendor? This action cannot be undone.'
/>
</> </>
) )
} }

View File

@ -1,5 +1,5 @@
// React Imports // React Imports
import { useState } from 'react' import { useState, useEffect } from 'react'
// MUI Imports // MUI Imports
import Button from '@mui/material/Button' import Button from '@mui/material/Button'
@ -18,12 +18,13 @@ import { useForm, Controller } from 'react-hook-form'
// Component Imports // Component Imports
import CustomTextField from '@core/components/mui/TextField' import CustomTextField from '@core/components/mui/TextField'
import { VendorRequest } from '@/types/services/vendor' import { Vendor, VendorRequest } from '@/types/services/vendor'
import { useVendorsMutation } from '@/services/mutations/vendor' import { useVendorsMutation } from '@/services/mutations/vendor'
type Props = { type Props = {
open: boolean open: boolean
handleClose: () => void handleClose: () => void
data?: Vendor // Data vendor untuk edit (jika ada)
} }
type FormValidateType = { type FormValidateType = {
@ -51,9 +52,9 @@ const initialData: FormValidateType = {
is_active: true is_active: true
} }
const AddVendorDrawer = (props: Props) => { const AddEditVendorDrawer = (props: Props) => {
// Props // Props
const { open, handleClose } = props const { open, handleClose, data } = props
// States // States
const [showMore, setShowMore] = useState(false) const [showMore, setShowMore] = useState(false)
@ -61,6 +62,9 @@ const AddVendorDrawer = (props: Props) => {
const { createVendor, updateVendor } = useVendorsMutation() const { createVendor, updateVendor } = useVendorsMutation()
// Determine if this is edit mode
const isEditMode = Boolean(data?.id)
// Hooks // Hooks
const { const {
control, control,
@ -71,29 +75,73 @@ const AddVendorDrawer = (props: Props) => {
defaultValues: initialData defaultValues: initialData
}) })
const handleFormSubmit = async (data: FormValidateType) => { // Effect to populate form when editing
useEffect(() => {
if (isEditMode && data) {
// Populate form with existing data
const formData: FormValidateType = {
name: data.name || '',
email: data.email || '',
phone_number: data.phone_number || '',
address: data.address || '',
contact_person: data.contact_person || '',
tax_number: data.tax_number || '',
payment_terms: data.payment_terms || '',
notes: data.notes || '',
is_active: data.is_active ?? true
}
resetForm(formData)
// Show more fields if any optional field has data
const hasOptionalData = data.address || data.tax_number || data.payment_terms || data.notes
if (hasOptionalData) {
setShowMore(true)
}
} else {
// Reset to initial data for add mode
resetForm(initialData)
setShowMore(false)
}
}, [data, isEditMode, resetForm])
const handleFormSubmit = async (formData: FormValidateType) => {
try { try {
setIsSubmitting(true) setIsSubmitting(true)
// Create VendorRequest object // Create VendorRequest object
const vendorRequest: VendorRequest = { const vendorRequest: VendorRequest = {
name: data.name, name: formData.name,
email: data.email || undefined, email: formData.email || undefined,
phone_number: data.phone_number || undefined, phone_number: formData.phone_number || undefined,
address: data.address || undefined, address: formData.address || undefined,
contact_person: data.contact_person || undefined, contact_person: formData.contact_person || undefined,
tax_number: data.tax_number || undefined, tax_number: formData.tax_number || undefined,
payment_terms: data.payment_terms || undefined, payment_terms: formData.payment_terms || undefined,
notes: data.notes || undefined, notes: formData.notes || undefined,
is_active: data.is_active is_active: formData.is_active
} }
if (isEditMode && data?.id) {
// Update existing vendor
updateVendor.mutate(
{ id: data.id, payload: vendorRequest },
{
onSuccess: () => {
handleReset()
handleClose()
}
}
)
} else {
// Create new vendor
createVendor.mutate(vendorRequest, { createVendor.mutate(vendorRequest, {
onSuccess: () => { onSuccess: () => {
handleReset() handleReset()
handleClose() handleClose()
} }
}) })
}
} catch (error) { } catch (error) {
console.error('Error submitting vendor:', error) console.error('Error submitting vendor:', error)
// Handle error (show toast, etc.) // Handle error (show toast, etc.)
@ -136,7 +184,7 @@ const AddVendorDrawer = (props: Props) => {
}} }}
> >
<div className='flex items-center justify-between plb-5 pli-6'> <div className='flex items-center justify-between plb-5 pli-6'>
<Typography variant='h5'>Tambah Vendor Baru</Typography> <Typography variant='h5'>{isEditMode ? 'Edit Vendor' : 'Tambah Vendor Baru'}</Typography>
<IconButton size='small' onClick={handleReset}> <IconButton size='small' onClick={handleReset}>
<i className='tabler-x text-2xl text-textPrimary' /> <i className='tabler-x text-2xl text-textPrimary' />
</IconButton> </IconButton>
@ -359,7 +407,7 @@ const AddVendorDrawer = (props: Props) => {
> >
<div className='flex items-center gap-4'> <div className='flex items-center gap-4'>
<Button variant='contained' type='submit' form='vendor-form' disabled={isSubmitting}> <Button variant='contained' type='submit' form='vendor-form' disabled={isSubmitting}>
{isSubmitting ? 'Menyimpan...' : 'Simpan'} {isSubmitting ? (isEditMode ? 'Mengupdate...' : 'Menyimpan...') : isEditMode ? 'Update' : 'Simpan'}
</Button> </Button>
<Button variant='outlined' color='error' onClick={handleReset} disabled={isSubmitting}> <Button variant='outlined' color='error' onClick={handleReset} disabled={isSubmitting}>
Batal Batal
@ -370,4 +418,4 @@ const AddVendorDrawer = (props: Props) => {
) )
} }
export default AddVendorDrawer export default AddEditVendorDrawer

View File

@ -183,12 +183,12 @@ const VendorListTable = () => {
cell: ({ row }) => ( cell: ({ row }) => (
<div className='flex items-center gap-4'> <div className='flex items-center gap-4'>
<div className='flex flex-col'> <div className='flex flex-col'>
<Link href={getLocalizedUrl(`/apps/vendor/detail`, locale as Locale)}> <Link href={getLocalizedUrl(`/apps/vendor/${row.original.id}/detail`, locale as Locale)}>
<Typography className='font-medium cursor-pointer hover:underline text-primary'> <Typography className='font-medium cursor-pointer hover:underline text-primary'>
{row.original.contact_person} {row.original.contact_person}
</Typography> </Typography>
</Link>
<Typography variant='body2'>{row.original.email}</Typography> <Typography variant='body2'>{row.original.email}</Typography>
</Link>
</div> </div>
</div> </div>
) )