pos-dashboard-v2/src/views/apps/account/AccountFormDrawer.tsx

397 lines
13 KiB
TypeScript
Raw Normal View History

2025-09-11 00:16:00 +07:00
// React Imports
import { useState, useEffect } from 'react'
// MUI Imports
import Button from '@mui/material/Button'
import Drawer from '@mui/material/Drawer'
import IconButton from '@mui/material/IconButton'
import Typography from '@mui/material/Typography'
import Box from '@mui/material/Box'
// Third-party Imports
import { useForm, Controller } from 'react-hook-form'
// Component Imports
import CustomTextField from '@core/components/mui/TextField'
import CustomAutocomplete from '@/@core/components/mui/Autocomplete'
2025-09-12 20:35:49 +07:00
import { AccountRequest } from '@/services/queries/chartOfAccountType'
import { useChartOfAccount } from '@/services/queries/chartOfAccount'
2025-09-12 20:35:49 +07:00
import { Account, ChartOfAccount } from '@/types/services/chartOfAccount'
import { useAccountsMutation } from '@/services/mutations/account'
2025-09-11 00:16:00 +07:00
type Props = {
open: boolean
handleClose: () => void
2025-09-12 20:35:49 +07:00
accountData?: Account[]
setData: (data: Account[]) => void
editingAccount?: Account | null
2025-09-11 00:16:00 +07:00
}
type FormValidateType = {
name: string
code: string
2025-09-12 20:35:49 +07:00
account_type: string
opening_balance: number
description: string
chart_of_account_id: string
2025-09-11 00:16:00 +07:00
}
// Vars
const initialData = {
name: '',
code: '',
2025-09-12 20:35:49 +07:00
account_type: '',
opening_balance: 0,
description: '',
chart_of_account_id: ''
2025-09-11 00:16:00 +07:00
}
2025-09-12 20:35:49 +07:00
// Static Account Types
const staticAccountTypes = [
{ id: '1', name: 'Cash', code: 'cash', description: 'Cash account' },
{ id: '2', name: 'Wallet', code: 'wallet', description: 'Digital wallet account' },
{ id: '3', name: 'Bank', code: 'bank', description: 'Bank account' },
{ id: '4', name: 'Credit', code: 'credit', description: 'Credit account' },
{ id: '5', name: 'Debit', code: 'debit', description: 'Debit account' },
{ id: '6', name: 'Asset', code: 'asset', description: 'Asset account' },
{ id: '7', name: 'Liability', code: 'liability', description: 'Liability account' },
{ id: '8', name: 'Equity', code: 'equity', description: 'Equity account' },
{ id: '9', name: 'Revenue', code: 'revenue', description: 'Revenue account' },
{ id: '10', name: 'Expense', code: 'expense', description: 'Expense account' }
]
2025-09-11 00:16:00 +07:00
const AccountFormDrawer = (props: Props) => {
// Props
const { open, handleClose, accountData, setData, editingAccount } = props
// Determine if we're editing
const isEdit = !!editingAccount
const { data: accounts, isLoading: isLoadingAccounts } = useChartOfAccount({
page: 1,
limit: 100
})
2025-09-12 20:35:49 +07:00
const { createAccount, updateAccount } = useAccountsMutation()
// Use static account types
const accountTypeOptions = staticAccountTypes
2025-09-12 19:18:43 +07:00
2025-09-12 20:35:49 +07:00
// Process chart of accounts for the dropdown
const chartOfAccountOptions = accounts?.data.length
? accounts.data
.filter(account => account.is_active) // Only show active accounts
.map(account => ({
id: account.id,
code: account.code,
name: account.name,
description: account.description
}))
: []
2025-09-11 00:16:00 +07:00
// Hooks
const {
control,
reset: resetForm,
handleSubmit,
formState: { errors }
} = useForm<FormValidateType>({
defaultValues: initialData
})
// Reset form when editingAccount changes or drawer opens
useEffect(() => {
if (open) {
if (editingAccount) {
// Populate form with existing data
resetForm({
name: editingAccount.name,
2025-09-12 20:35:49 +07:00
code: editingAccount.number,
account_type: editingAccount.account_type,
opening_balance: editingAccount.opening_balance,
description: editingAccount.description || '',
chart_of_account_id: editingAccount.chart_of_account_id
2025-09-11 00:16:00 +07:00
})
} else {
// Reset to initial data for new account
resetForm(initialData)
}
}
}, [editingAccount, open, resetForm])
const onSubmit = (data: FormValidateType) => {
if (isEdit && editingAccount) {
2025-09-12 20:35:49 +07:00
const accountRequest: AccountRequest = {
chart_of_account_id: data.chart_of_account_id,
name: data.name,
number: data.code,
account_type: data.account_type,
opening_balance: data.opening_balance,
description: data.description
}
updateAccount.mutate(
{ id: editingAccount.id, payload: accountRequest },
{
onSuccess: () => {
handleClose()
resetForm(initialData)
}
}
)
2025-09-11 00:16:00 +07:00
} else {
2025-09-12 20:35:49 +07:00
// Create new account - this would typically be sent as AccountRequest to API
const accountRequest: AccountRequest = {
chart_of_account_id: data.chart_of_account_id,
2025-09-11 00:16:00 +07:00
name: data.name,
2025-09-12 20:35:49 +07:00
number: data.code,
account_type: data.account_type,
opening_balance: data.opening_balance,
description: data.description
2025-09-11 00:16:00 +07:00
}
2025-09-12 20:35:49 +07:00
createAccount.mutate(accountRequest, {
onSuccess: () => {
handleClose()
resetForm(initialData)
}
})
2025-09-11 00:16:00 +07:00
}
}
const handleReset = () => {
handleClose()
resetForm(initialData)
}
return (
<Drawer
open={open}
anchor='right'
variant='temporary'
onClose={handleReset}
ModalProps={{ keepMounted: true }}
sx={{
'& .MuiDrawer-paper': {
width: { xs: 300, sm: 400 },
display: 'flex',
flexDirection: 'column',
height: '100%'
}
}}
>
{/* Sticky Header */}
<Box
sx={{
position: 'sticky',
top: 0,
zIndex: 10,
backgroundColor: 'background.paper',
borderBottom: 1,
borderColor: 'divider'
}}
>
<div className='flex items-center justify-between plb-5 pli-6'>
<Typography variant='h5'>{isEdit ? 'Edit Akun' : 'Tambah Akun Baru'}</Typography>
<IconButton size='small' onClick={handleReset}>
<i className='tabler-x text-2xl text-textPrimary' />
</IconButton>
</div>
</Box>
{/* Scrollable Content */}
<Box sx={{ flex: 1, overflowY: 'auto' }}>
<form id='account-form' onSubmit={handleSubmit(data => onSubmit(data))}>
<div className='flex flex-col gap-6 p-6'>
{/* Nama */}
<div>
<Typography variant='body2' className='mb-2'>
Nama <span className='text-red-500'>*</span>
</Typography>
<Controller
name='name'
control={control}
rules={{ required: true }}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
placeholder='Nama'
{...(errors.name && { error: true, helperText: 'Field ini wajib diisi.' })}
/>
)}
/>
</div>
{/* Kode */}
<div>
<Typography variant='body2' className='mb-2'>
Kode <span className='text-red-500'>*</span>
</Typography>
<Controller
name='code'
control={control}
rules={{ required: true }}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
placeholder='Kode'
{...(errors.code && { error: true, helperText: 'Field ini wajib diisi.' })}
/>
)}
/>
</div>
2025-09-12 20:35:49 +07:00
{/* Tipe Akun */}
2025-09-11 00:16:00 +07:00
<div>
<Typography variant='body2' className='mb-2'>
2025-09-12 20:35:49 +07:00
Tipe Akun <span className='text-red-500'>*</span>
2025-09-11 00:16:00 +07:00
</Typography>
<Controller
2025-09-12 20:35:49 +07:00
name='account_type'
2025-09-11 00:16:00 +07:00
control={control}
rules={{ required: true }}
render={({ field: { onChange, value, ...field } }) => (
<CustomAutocomplete
{...field}
2025-09-12 20:35:49 +07:00
options={accountTypeOptions}
value={accountTypeOptions.find(option => option.code === value) || null}
onChange={(_, newValue) => onChange(newValue?.code || '')}
2025-09-12 19:18:43 +07:00
getOptionLabel={option => option.name}
renderOption={(props, option) => (
<Box component='li' {...props}>
<div>
2025-09-12 20:35:49 +07:00
<Typography variant='body2'>{option.name}</Typography>
2025-09-12 19:18:43 +07:00
</div>
</Box>
)}
2025-09-11 00:16:00 +07:00
renderInput={params => (
<CustomTextField
{...params}
2025-09-12 20:35:49 +07:00
placeholder='Pilih tipe akun'
{...(errors.account_type && { error: true, helperText: 'Field ini wajib diisi.' })}
2025-09-11 00:16:00 +07:00
/>
)}
2025-09-12 20:35:49 +07:00
isOptionEqualToValue={(option, value) => option.code === value.code}
2025-09-11 00:16:00 +07:00
/>
)}
/>
</div>
2025-09-12 20:35:49 +07:00
{/* Chart of Account */}
2025-09-11 00:16:00 +07:00
<div>
<Typography variant='body2' className='mb-2'>
2025-09-12 20:35:49 +07:00
Chart of Account <span className='text-red-500'>*</span>
2025-09-11 00:16:00 +07:00
</Typography>
<Controller
2025-09-12 20:35:49 +07:00
name='chart_of_account_id'
2025-09-11 00:16:00 +07:00
control={control}
2025-09-12 20:35:49 +07:00
rules={{ required: true }}
2025-09-11 00:16:00 +07:00
render={({ field: { onChange, value, ...field } }) => (
<CustomAutocomplete
{...field}
loading={isLoadingAccounts}
2025-09-12 20:35:49 +07:00
options={chartOfAccountOptions}
value={chartOfAccountOptions.find(option => option.id === value) || null}
onChange={(_, newValue) => onChange(newValue?.id || '')}
getOptionLabel={option => `${option.code} - ${option.name}`}
renderOption={(props, option) => (
<Box component='li' {...props}>
<div>
<Typography variant='body2'>
{option.code} - {option.name}
</Typography>
{option.description && (
<Typography variant='caption' color='textSecondary'>
{option.description}
</Typography>
)}
</div>
</Box>
)}
renderInput={params => (
<CustomTextField
{...params}
2025-09-12 20:35:49 +07:00
placeholder={isLoadingAccounts ? 'Loading chart of accounts...' : 'Pilih chart of account'}
{...(errors.chart_of_account_id && { error: true, helperText: 'Field ini wajib diisi.' })}
/>
)}
2025-09-12 20:35:49 +07:00
isOptionEqualToValue={(option, value) => option.id === value.id}
disabled={isLoadingAccounts}
2025-09-12 20:35:49 +07:00
noOptionsText={isLoadingAccounts ? 'Loading...' : 'Tidak ada chart of account tersedia'}
/>
)}
/>
</div>
{/* Opening Balance */}
<div>
<Typography variant='body2' className='mb-2'>
Saldo Awal <span className='text-red-500'>*</span>
</Typography>
<Controller
name='opening_balance'
control={control}
rules={{ required: true, min: 0 }}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
type='number'
placeholder='0'
onChange={e => field.onChange(Number(e.target.value))}
{...(errors.opening_balance && {
error: true,
helperText:
errors.opening_balance.type === 'min'
? 'Saldo awal tidak boleh negatif.'
: 'Field ini wajib diisi.'
})}
2025-09-11 00:16:00 +07:00
/>
)}
/>
</div>
2025-09-12 20:35:49 +07:00
{/* Deskripsi */}
<div>
<Typography variant='body2' className='mb-2'>
Deskripsi
</Typography>
<Controller
name='description'
control={control}
render={({ field }) => (
<CustomTextField {...field} fullWidth multiline rows={3} placeholder='Deskripsi akun' />
)}
/>
</div>
2025-09-11 00:16:00 +07:00
</div>
</form>
</Box>
{/* Sticky Footer */}
<Box
sx={{
position: 'sticky',
bottom: 0,
zIndex: 10,
backgroundColor: 'background.paper',
borderTop: 1,
borderColor: 'divider',
p: 3
}}
>
<div className='flex items-center gap-4'>
<Button variant='contained' type='submit' form='account-form'>
{isEdit ? 'Update' : 'Simpan'}
</Button>
<Button variant='tonal' color='error' onClick={() => handleReset()}>
Batal
</Button>
</div>
</Box>
</Drawer>
)
}
export default AccountFormDrawer