Create And Update Account
This commit is contained in:
parent
ce344e4a98
commit
b2840ca27c
52
src/services/mutations/account.ts
Normal file
52
src/services/mutations/account.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import { api } from '../api'
|
||||||
|
import { AccountRequest } from '../queries/chartOfAccountType'
|
||||||
|
|
||||||
|
export const useAccountsMutation = () => {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
const createAccount = useMutation({
|
||||||
|
mutationFn: async (newAccount: AccountRequest) => {
|
||||||
|
const response = await api.post('/accounts', newAccount)
|
||||||
|
return response.data
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('Account created successfully!')
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['accounts'] })
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateAccount = useMutation({
|
||||||
|
mutationFn: async ({ id, payload }: { id: string; payload: AccountRequest }) => {
|
||||||
|
const response = await api.put(`/accounts/${id}`, payload)
|
||||||
|
return response.data
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('Account updated successfully!')
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['accounts'] })
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast.error(error.response?.data?.errors?.[0]?.cause || 'Update failed')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const deleteAccount = useMutation({
|
||||||
|
mutationFn: async (id: string) => {
|
||||||
|
const response = await api.delete(`/accounts/${id}`)
|
||||||
|
return response.data
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('Account deleted successfully!')
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['accounts'] })
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast.error(error.response?.data?.errors?.[0]?.cause || 'Delete failed')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return { createAccount, updateAccount, deleteAccount }
|
||||||
|
}
|
||||||
@ -34,3 +34,12 @@ export function useChartOfAccountTypes(params: ChartOfAccountQueryParams = {}) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AccountRequest {
|
||||||
|
chart_of_account_id: string
|
||||||
|
name: string
|
||||||
|
number: string
|
||||||
|
account_type: string
|
||||||
|
opening_balance: number
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|||||||
@ -14,41 +14,52 @@ 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 CustomAutocomplete from '@/@core/components/mui/Autocomplete'
|
import CustomAutocomplete from '@/@core/components/mui/Autocomplete'
|
||||||
import { useChartOfAccountTypes } from '@/services/queries/chartOfAccountType'
|
import { AccountRequest } from '@/services/queries/chartOfAccountType'
|
||||||
import { useChartOfAccount } from '@/services/queries/chartOfAccount'
|
import { useChartOfAccount } from '@/services/queries/chartOfAccount'
|
||||||
|
import { Account, ChartOfAccount } from '@/types/services/chartOfAccount'
|
||||||
// Account Type
|
import { useAccountsMutation } from '@/services/mutations/account'
|
||||||
export type AccountType = {
|
|
||||||
id: number
|
|
||||||
code: string
|
|
||||||
name: string
|
|
||||||
category: string
|
|
||||||
balance: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open: boolean
|
open: boolean
|
||||||
handleClose: () => void
|
handleClose: () => void
|
||||||
accountData?: AccountType[]
|
accountData?: Account[]
|
||||||
setData: (data: AccountType[]) => void
|
setData: (data: Account[]) => void
|
||||||
editingAccount?: AccountType | null
|
editingAccount?: Account | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type FormValidateType = {
|
type FormValidateType = {
|
||||||
name: string
|
name: string
|
||||||
code: string
|
code: string
|
||||||
category: string
|
account_type: string
|
||||||
parentAccount?: string
|
opening_balance: number
|
||||||
|
description: string
|
||||||
|
chart_of_account_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vars
|
// Vars
|
||||||
const initialData = {
|
const initialData = {
|
||||||
name: '',
|
name: '',
|
||||||
code: '',
|
code: '',
|
||||||
category: '',
|
account_type: '',
|
||||||
parentAccount: ''
|
opening_balance: 0,
|
||||||
|
description: '',
|
||||||
|
chart_of_account_id: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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' }
|
||||||
|
]
|
||||||
|
|
||||||
const AccountFormDrawer = (props: Props) => {
|
const AccountFormDrawer = (props: Props) => {
|
||||||
// Props
|
// Props
|
||||||
const { open, handleClose, accountData, setData, editingAccount } = props
|
const { open, handleClose, accountData, setData, editingAccount } = props
|
||||||
@ -56,30 +67,20 @@ const AccountFormDrawer = (props: Props) => {
|
|||||||
// Determine if we're editing
|
// Determine if we're editing
|
||||||
const isEdit = !!editingAccount
|
const isEdit = !!editingAccount
|
||||||
|
|
||||||
const { data: accountTypes, isLoading } = useChartOfAccountTypes()
|
|
||||||
|
|
||||||
const { data: accounts, isLoading: isLoadingAccounts } = useChartOfAccount({
|
const { data: accounts, isLoading: isLoadingAccounts } = useChartOfAccount({
|
||||||
page: 1,
|
page: 1,
|
||||||
limit: 100
|
limit: 100
|
||||||
})
|
})
|
||||||
|
|
||||||
// Process account types for the dropdown
|
const { createAccount, updateAccount } = useAccountsMutation()
|
||||||
const categoryOptions = accountTypes?.data.length
|
|
||||||
? accountTypes.data
|
|
||||||
.filter(type => type.is_active) // Only show active types
|
|
||||||
.map(type => ({
|
|
||||||
id: type.id,
|
|
||||||
name: type.name,
|
|
||||||
code: type.code,
|
|
||||||
description: type.description
|
|
||||||
}))
|
|
||||||
: []
|
|
||||||
|
|
||||||
// Process accounts for parent account dropdown
|
// Use static account types
|
||||||
const parentAccountOptions = accounts?.data.length
|
const accountTypeOptions = staticAccountTypes
|
||||||
|
|
||||||
|
// Process chart of accounts for the dropdown
|
||||||
|
const chartOfAccountOptions = accounts?.data.length
|
||||||
? accounts.data
|
? accounts.data
|
||||||
.filter(account => account.is_active) // Only show active accounts
|
.filter(account => account.is_active) // Only show active accounts
|
||||||
.filter(account => (editingAccount ? account.id !== editingAccount.id.toString() : true)) // Exclude current account when editing
|
|
||||||
.map(account => ({
|
.map(account => ({
|
||||||
id: account.id,
|
id: account.id,
|
||||||
code: account.code,
|
code: account.code,
|
||||||
@ -105,9 +106,11 @@ const AccountFormDrawer = (props: Props) => {
|
|||||||
// Populate form with existing data
|
// Populate form with existing data
|
||||||
resetForm({
|
resetForm({
|
||||||
name: editingAccount.name,
|
name: editingAccount.name,
|
||||||
code: editingAccount.code,
|
code: editingAccount.number,
|
||||||
category: editingAccount.category,
|
account_type: editingAccount.account_type,
|
||||||
parentAccount: ''
|
opening_balance: editingAccount.opening_balance,
|
||||||
|
description: editingAccount.description || '',
|
||||||
|
chart_of_account_id: editingAccount.chart_of_account_id
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Reset to initial data for new account
|
// Reset to initial data for new account
|
||||||
@ -118,36 +121,41 @@ const AccountFormDrawer = (props: Props) => {
|
|||||||
|
|
||||||
const onSubmit = (data: FormValidateType) => {
|
const onSubmit = (data: FormValidateType) => {
|
||||||
if (isEdit && editingAccount) {
|
if (isEdit && editingAccount) {
|
||||||
// Update existing account
|
const accountRequest: AccountRequest = {
|
||||||
const updatedAccounts =
|
chart_of_account_id: data.chart_of_account_id,
|
||||||
accountData?.map(account =>
|
|
||||||
account.id === editingAccount.id
|
|
||||||
? {
|
|
||||||
...account,
|
|
||||||
code: data.code,
|
|
||||||
name: data.name,
|
name: data.name,
|
||||||
category: data.category
|
number: data.code,
|
||||||
|
account_type: data.account_type,
|
||||||
|
opening_balance: data.opening_balance,
|
||||||
|
description: data.description
|
||||||
}
|
}
|
||||||
: account
|
updateAccount.mutate(
|
||||||
) || []
|
{ id: editingAccount.id, payload: accountRequest },
|
||||||
|
{
|
||||||
setData(updatedAccounts)
|
onSuccess: () => {
|
||||||
} else {
|
|
||||||
// Create new account
|
|
||||||
const newAccount: AccountType = {
|
|
||||||
id: accountData?.length ? Math.max(...accountData.map(a => a.id)) + 1 : 1,
|
|
||||||
code: data.code,
|
|
||||||
name: data.name,
|
|
||||||
category: data.category,
|
|
||||||
balance: '0'
|
|
||||||
}
|
|
||||||
|
|
||||||
setData([...(accountData ?? []), newAccount])
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClose()
|
handleClose()
|
||||||
resetForm(initialData)
|
resetForm(initialData)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Create new account - this would typically be sent as AccountRequest to API
|
||||||
|
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
|
||||||
|
}
|
||||||
|
createAccount.mutate(accountRequest, {
|
||||||
|
onSuccess: () => {
|
||||||
|
handleClose()
|
||||||
|
resetForm(initialData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
handleClose()
|
handleClose()
|
||||||
@ -233,61 +241,58 @@ const AccountFormDrawer = (props: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Kategori */}
|
{/* Tipe Akun */}
|
||||||
<div>
|
<div>
|
||||||
<Typography variant='body2' className='mb-2'>
|
<Typography variant='body2' className='mb-2'>
|
||||||
Kategori <span className='text-red-500'>*</span>
|
Tipe Akun <span className='text-red-500'>*</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Controller
|
<Controller
|
||||||
name='category'
|
name='account_type'
|
||||||
control={control}
|
control={control}
|
||||||
rules={{ required: true }}
|
rules={{ required: true }}
|
||||||
render={({ field: { onChange, value, ...field } }) => (
|
render={({ field: { onChange, value, ...field } }) => (
|
||||||
<CustomAutocomplete
|
<CustomAutocomplete
|
||||||
{...field}
|
{...field}
|
||||||
loading={isLoading}
|
options={accountTypeOptions}
|
||||||
options={categoryOptions}
|
value={accountTypeOptions.find(option => option.code === value) || null}
|
||||||
value={categoryOptions.find(option => option.name === value) || null}
|
onChange={(_, newValue) => onChange(newValue?.code || '')}
|
||||||
onChange={(_, newValue) => onChange(newValue?.name || '')}
|
|
||||||
getOptionLabel={option => option.name}
|
getOptionLabel={option => option.name}
|
||||||
renderOption={(props, option) => (
|
renderOption={(props, option) => (
|
||||||
<Box component='li' {...props}>
|
<Box component='li' {...props}>
|
||||||
<div>
|
<div>
|
||||||
<Typography variant='body2'>
|
<Typography variant='body2'>{option.name}</Typography>
|
||||||
{option.code} - {option.name}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
renderInput={params => (
|
renderInput={params => (
|
||||||
<CustomTextField
|
<CustomTextField
|
||||||
{...params}
|
{...params}
|
||||||
placeholder={isLoading ? 'Loading categories...' : 'Pilih kategori'}
|
placeholder='Pilih tipe akun'
|
||||||
{...(errors.category && { error: true, helperText: 'Field ini wajib diisi.' })}
|
{...(errors.account_type && { error: true, helperText: 'Field ini wajib diisi.' })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
isOptionEqualToValue={(option, value) => option.name === value.name}
|
isOptionEqualToValue={(option, value) => option.code === value.code}
|
||||||
disabled={isLoading}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sub Akun dari */}
|
{/* Chart of Account */}
|
||||||
<div>
|
<div>
|
||||||
<Typography variant='body2' className='mb-2'>
|
<Typography variant='body2' className='mb-2'>
|
||||||
Sub Akun dari
|
Chart of Account <span className='text-red-500'>*</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Controller
|
<Controller
|
||||||
name='parentAccount'
|
name='chart_of_account_id'
|
||||||
control={control}
|
control={control}
|
||||||
|
rules={{ required: true }}
|
||||||
render={({ field: { onChange, value, ...field } }) => (
|
render={({ field: { onChange, value, ...field } }) => (
|
||||||
<CustomAutocomplete
|
<CustomAutocomplete
|
||||||
{...field}
|
{...field}
|
||||||
loading={isLoadingAccounts}
|
loading={isLoadingAccounts}
|
||||||
options={parentAccountOptions}
|
options={chartOfAccountOptions}
|
||||||
value={parentAccountOptions.find(account => `${account.code} ${account.name}` === value) || null}
|
value={chartOfAccountOptions.find(option => option.id === value) || null}
|
||||||
onChange={(_, newValue) => onChange(newValue ? `${newValue.code} ${newValue.name}` : '')}
|
onChange={(_, newValue) => onChange(newValue?.id || '')}
|
||||||
getOptionLabel={option => `${option.code} - ${option.name}`}
|
getOptionLabel={option => `${option.code} - ${option.name}`}
|
||||||
renderOption={(props, option) => (
|
renderOption={(props, option) => (
|
||||||
<Box component='li' {...props}>
|
<Box component='li' {...props}>
|
||||||
@ -306,18 +311,59 @@ const AccountFormDrawer = (props: Props) => {
|
|||||||
renderInput={params => (
|
renderInput={params => (
|
||||||
<CustomTextField
|
<CustomTextField
|
||||||
{...params}
|
{...params}
|
||||||
placeholder={isLoadingAccounts ? 'Loading accounts...' : 'Pilih akun parent'}
|
placeholder={isLoadingAccounts ? 'Loading chart of accounts...' : 'Pilih chart of account'}
|
||||||
|
{...(errors.chart_of_account_id && { error: true, helperText: 'Field ini wajib diisi.' })}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
isOptionEqualToValue={(option, value) =>
|
isOptionEqualToValue={(option, value) => option.id === value.id}
|
||||||
`${option.code} ${option.name}` === `${value.code} ${value.name}`
|
|
||||||
}
|
|
||||||
disabled={isLoadingAccounts}
|
disabled={isLoadingAccounts}
|
||||||
noOptionsText={isLoadingAccounts ? 'Loading...' : 'Tidak ada akun tersedia'}
|
noOptionsText={isLoadingAccounts ? 'Loading...' : 'Tidak ada chart of account tersedia'}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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.'
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 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>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -226,6 +226,10 @@ const AccountListTable = () => {
|
|||||||
variant='text'
|
variant='text'
|
||||||
color='primary'
|
color='primary'
|
||||||
className='p-0 min-w-0 font-medium normal-case justify-start'
|
className='p-0 min-w-0 font-medium normal-case justify-start'
|
||||||
|
onClick={() => {
|
||||||
|
setEditingAccount(row.original)
|
||||||
|
setAddAccountOpen(true)
|
||||||
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
@ -414,13 +418,13 @@ const AccountListTable = () => {
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
{/* <AccountFormDrawer
|
<AccountFormDrawer
|
||||||
open={addAccountOpen}
|
open={addAccountOpen}
|
||||||
handleClose={handleCloseDrawer}
|
handleClose={handleCloseDrawer}
|
||||||
accountData={data}
|
accountData={accounts}
|
||||||
setData={setData}
|
setData={() => {}}
|
||||||
editingAccount={editingAccount}
|
editingAccount={editingAccount}
|
||||||
/> */}
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user