diff --git a/src/services/mutations/account.ts b/src/services/mutations/account.ts new file mode 100644 index 0000000..1f8616c --- /dev/null +++ b/src/services/mutations/account.ts @@ -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 } +} diff --git a/src/services/queries/chartOfAccountType.ts b/src/services/queries/chartOfAccountType.ts index 0173626..504c9f0 100644 --- a/src/services/queries/chartOfAccountType.ts +++ b/src/services/queries/chartOfAccountType.ts @@ -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 +} diff --git a/src/views/apps/account/AccountFormDrawer.tsx b/src/views/apps/account/AccountFormDrawer.tsx index 64f2c8a..2651457 100644 --- a/src/views/apps/account/AccountFormDrawer.tsx +++ b/src/views/apps/account/AccountFormDrawer.tsx @@ -14,41 +14,52 @@ import { useForm, Controller } from 'react-hook-form' // Component Imports import CustomTextField from '@core/components/mui/TextField' 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' - -// Account Type -export type AccountType = { - id: number - code: string - name: string - category: string - balance: string -} +import { Account, ChartOfAccount } from '@/types/services/chartOfAccount' +import { useAccountsMutation } from '@/services/mutations/account' type Props = { open: boolean handleClose: () => void - accountData?: AccountType[] - setData: (data: AccountType[]) => void - editingAccount?: AccountType | null + accountData?: Account[] + setData: (data: Account[]) => void + editingAccount?: Account | null } type FormValidateType = { name: string code: string - category: string - parentAccount?: string + account_type: string + opening_balance: number + description: string + chart_of_account_id: string } // Vars const initialData = { name: '', code: '', - category: '', - parentAccount: '' + account_type: '', + 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) => { // Props const { open, handleClose, accountData, setData, editingAccount } = props @@ -56,30 +67,20 @@ const AccountFormDrawer = (props: Props) => { // Determine if we're editing const isEdit = !!editingAccount - const { data: accountTypes, isLoading } = useChartOfAccountTypes() - const { data: accounts, isLoading: isLoadingAccounts } = useChartOfAccount({ page: 1, limit: 100 }) - // Process account types for the dropdown - 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 - })) - : [] + const { createAccount, updateAccount } = useAccountsMutation() - // Process accounts for parent account dropdown - const parentAccountOptions = accounts?.data.length + // Use static account types + const accountTypeOptions = staticAccountTypes + + // Process chart of accounts for the dropdown + const chartOfAccountOptions = accounts?.data.length ? accounts.data .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 => ({ id: account.id, code: account.code, @@ -105,9 +106,11 @@ const AccountFormDrawer = (props: Props) => { // Populate form with existing data resetForm({ name: editingAccount.name, - code: editingAccount.code, - category: editingAccount.category, - parentAccount: '' + 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 }) } else { // Reset to initial data for new account @@ -118,35 +121,40 @@ const AccountFormDrawer = (props: Props) => { const onSubmit = (data: FormValidateType) => { if (isEdit && editingAccount) { - // Update existing account - const updatedAccounts = - accountData?.map(account => - account.id === editingAccount.id - ? { - ...account, - code: data.code, - name: data.name, - category: data.category - } - : account - ) || [] - - setData(updatedAccounts) - } else { - // Create new account - const newAccount: AccountType = { - id: accountData?.length ? Math.max(...accountData.map(a => a.id)) + 1 : 1, - code: data.code, + const accountRequest: AccountRequest = { + chart_of_account_id: data.chart_of_account_id, name: data.name, - category: data.category, - balance: '0' + number: data.code, + account_type: data.account_type, + opening_balance: data.opening_balance, + description: data.description } - - setData([...(accountData ?? []), newAccount]) + updateAccount.mutate( + { id: editingAccount.id, payload: accountRequest }, + { + onSuccess: () => { + handleClose() + 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) + } + }) } - - handleClose() - resetForm(initialData) } const handleReset = () => { @@ -233,61 +241,58 @@ const AccountFormDrawer = (props: Props) => { /> - {/* Kategori */} + {/* Tipe Akun */}
- Kategori * + Tipe Akun * ( option.name === value) || null} - onChange={(_, newValue) => onChange(newValue?.name || '')} + options={accountTypeOptions} + value={accountTypeOptions.find(option => option.code === value) || null} + onChange={(_, newValue) => onChange(newValue?.code || '')} getOptionLabel={option => option.name} renderOption={(props, option) => (
- - {option.code} - {option.name} - + {option.name}
)} renderInput={params => ( )} - isOptionEqualToValue={(option, value) => option.name === value.name} - disabled={isLoading} + isOptionEqualToValue={(option, value) => option.code === value.code} /> )} />
- {/* Sub Akun dari */} + {/* Chart of Account */}
- Sub Akun dari + Chart of Account * ( `${account.code} ${account.name}` === value) || null} - onChange={(_, newValue) => onChange(newValue ? `${newValue.code} ${newValue.name}` : '')} + options={chartOfAccountOptions} + value={chartOfAccountOptions.find(option => option.id === value) || null} + onChange={(_, newValue) => onChange(newValue?.id || '')} getOptionLabel={option => `${option.code} - ${option.name}`} renderOption={(props, option) => ( @@ -306,18 +311,59 @@ const AccountFormDrawer = (props: Props) => { renderInput={params => ( )} - isOptionEqualToValue={(option, value) => - `${option.code} ${option.name}` === `${value.code} ${value.name}` - } + isOptionEqualToValue={(option, value) => option.id === value.id} disabled={isLoadingAccounts} - noOptionsText={isLoadingAccounts ? 'Loading...' : 'Tidak ada akun tersedia'} + noOptionsText={isLoadingAccounts ? 'Loading...' : 'Tidak ada chart of account tersedia'} /> )} />
+ + {/* Opening Balance */} +
+ + Saldo Awal * + + ( + 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.' + })} + /> + )} + /> +
+ + {/* Deskripsi */} +
+ + Deskripsi + + ( + + )} + /> +
diff --git a/src/views/apps/account/AccountListTable.tsx b/src/views/apps/account/AccountListTable.tsx index 8a11b5e..5ceabc8 100644 --- a/src/views/apps/account/AccountListTable.tsx +++ b/src/views/apps/account/AccountListTable.tsx @@ -226,6 +226,10 @@ const AccountListTable = () => { variant='text' color='primary' className='p-0 min-w-0 font-medium normal-case justify-start' + onClick={() => { + setEditingAccount(row.original) + setAddAccountOpen(true) + }} sx={{ textTransform: 'none', fontWeight: 500, @@ -414,13 +418,13 @@ const AccountListTable = () => { disabled={isLoading} /> - {/* {}} editingAccount={editingAccount} - /> */} + /> ) }