diff --git a/src/services/mutations/purchaseOrder.ts b/src/services/mutations/purchaseOrder.ts index 7ec250f..20356b8 100644 --- a/src/services/mutations/purchaseOrder.ts +++ b/src/services/mutations/purchaseOrder.ts @@ -1,7 +1,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query' import { toast } from 'react-toastify' import { api } from '../api' -import { PurchaseOrderRequest } from '@/types/services/purchaseOrder' +import { PurchaseOrderRequest, SendPaymentPurchaseOrderRequest } from '@/types/services/purchaseOrder' export const usePurchaseOrdersMutation = () => { const queryClient = useQueryClient() @@ -20,5 +20,19 @@ export const usePurchaseOrdersMutation = () => { } }) - return { createPurchaseOrder } + const sendPaymentPurchaseOrder = useMutation({ + mutationFn: async ({ id, payload }: { id: string; payload: SendPaymentPurchaseOrderRequest }) => { + const response = await api.put(`/purchase-orders/${id}`, payload) + return response.data + }, + onSuccess: () => { + toast.success('Purchase Order created successfully!') + queryClient.invalidateQueries({ queryKey: ['purchase-orders'] }) + }, + onError: (error: any) => { + toast.error(error.response?.data?.errors?.[0]?.cause || 'Create failed') + } + }) + + return { createPurchaseOrder, sendPaymentPurchaseOrder } } diff --git a/src/types/services/purchaseOrder.ts b/src/types/services/purchaseOrder.ts index 92a3def..1be12db 100644 --- a/src/types/services/purchaseOrder.ts +++ b/src/types/services/purchaseOrder.ts @@ -118,3 +118,9 @@ export interface PurchaseOrderFormItem { amount: number total: number // calculated field for UI } + +export interface SendPaymentPurchaseOrderRequest { + reference?: string + status?: 'received' + attachment_file_ids?: string[] // uuid.UUID[] +} diff --git a/src/views/apps/purchase/purchase-detail/PurchaseDetailContent.tsx b/src/views/apps/purchase/purchase-detail/PurchaseDetailContent.tsx index 7d41779..a433a36 100644 --- a/src/views/apps/purchase/purchase-detail/PurchaseDetailContent.tsx +++ b/src/views/apps/purchase/purchase-detail/PurchaseDetailContent.tsx @@ -12,6 +12,7 @@ import Loading from '@/components/layout/shared/Loading' const PurchaseDetailContent = () => { const params = useParams() const { data, isLoading, error, isFetching } = usePurchaseOrderById(params.id as string) + const total = (data?.items ?? []).reduce((sum, item) => sum + (item?.amount ?? 0) * item?.quantity, 0) return ( <> {isLoading ? ( @@ -23,7 +24,7 @@ const PurchaseDetailContent = () => { {data?.status == 'sent' && ( - + )} {/* diff --git a/src/views/apps/purchase/purchase-detail/PurchaseDetailSendPayment.tsx b/src/views/apps/purchase/purchase-detail/PurchaseDetailSendPayment.tsx index 6f64a4a..1bdd7dd 100644 --- a/src/views/apps/purchase/purchase-detail/PurchaseDetailSendPayment.tsx +++ b/src/views/apps/purchase/purchase-detail/PurchaseDetailSendPayment.tsx @@ -16,104 +16,130 @@ import { } from '@mui/material' import Grid from '@mui/material/Grid2' import CustomTextField from '@/@core/components/mui/TextField' -import CustomAutocomplete from '@/@core/components/mui/Autocomplete' +import ImageUpload from '@/components/ImageUpload' +import { useFilesMutation } from '@/services/mutations/files' +import { usePurchaseOrdersMutation } from '@/services/mutations/purchaseOrder' + +// API Interface +export interface SendPaymentPurchaseOrderRequest { + reference?: string + status?: 'received' + attachment_file_ids?: string[] // uuid.UUID[] +} interface PaymentFormData { totalDibayar: string - tglTransaksi: string referensi: string nomor: string - dibayarDari: string } -interface PemotonganItem { - id: string - dipotong: string - persentase: string - nominal: string - tipe: 'persen' | 'rupiah' +interface PurchaseDetailSendPaymentProps { + id?: string + totalAmount?: number + purchaseOrderNumber?: string + onSubmit?: (data: SendPaymentPurchaseOrderRequest) => Promise + loading?: boolean } -const PurchaseDetailSendPayment: React.FC = () => { +const PurchaseDetailSendPayment: React.FC = ({ + id, + totalAmount = 849000, + purchaseOrderNumber = 'PP/00025', + onSubmit, + loading = false +}) => { const [formData, setFormData] = useState({ - totalDibayar: '849.000', - tglTransaksi: '10/09/2025', + totalDibayar: totalAmount.toString(), referensi: '', - nomor: 'PP/00025', - dibayarDari: '1-10001 Kas' + nomor: purchaseOrderNumber }) const [expanded, setExpanded] = useState(false) - const [pemotonganItems, setPemotonganItems] = useState([]) + const [uploadedFileIds, setUploadedFileIds] = useState([]) + const [imageUrl, setImageUrl] = useState('') + const [isSubmitting, setIsSubmitting] = useState(false) - const dibayarDariOptions = [ - { label: '1-10001 Kas', value: '1-10001 Kas' }, - { label: '1-10002 Bank BCA', value: '1-10002 Bank BCA' }, - { label: '1-10003 Bank Mandiri', value: '1-10003 Bank Mandiri' }, - { label: '1-10004 Petty Cash', value: '1-10004 Petty Cash' } - ] - - const pemotonganOptions = [ - { label: 'PPN 11%', value: 'ppn' }, - { label: 'PPh 21', value: 'pph21' }, - { label: 'PPh 23', value: 'pph23' }, - { label: 'Biaya Admin', value: 'admin' } - ] + const { mutate, isPending } = useFilesMutation().uploadFile + const { sendPaymentPurchaseOrder } = usePurchaseOrdersMutation() const handleChange = - (field: keyof PaymentFormData) => (event: React.ChangeEvent | any) => { + (field: keyof PaymentFormData) => (event: React.ChangeEvent) => { setFormData(prev => ({ ...prev, [field]: event.target.value })) } - const handleDibayarDariChange = (value: { label: string; value: string } | null) => { - setFormData(prev => ({ - ...prev, - dibayarDari: value?.value || '' - })) - } - - const addPemotongan = () => { - const newItem: PemotonganItem = { - id: Date.now().toString(), - dipotong: '', - persentase: '0', - nominal: '', - tipe: 'persen' - } - setPemotonganItems(prev => [...prev, newItem]) - } - - const removePemotongan = (id: string) => { - setPemotonganItems(prev => prev.filter(item => item.id !== id)) - } - - const updatePemotongan = (id: string, field: keyof PemotonganItem, value: string) => { - setPemotonganItems(prev => prev.map(item => (item.id === id ? { ...item, [field]: value } : item))) - } - const handleAccordionChange = () => { setExpanded(!expanded) } - const calculatePemotongan = (item: PemotonganItem): number => { - const totalDibayar = parseInt(formData.totalDibayar.replace(/\D/g, '')) || 0 - const nilai = parseFloat(item.persentase) || 0 - - if (item.tipe === 'persen') { - return (totalDibayar * nilai) / 100 - } else { - return nilai - } - } - const formatCurrency = (amount: string | number): string => { const numAmount = typeof amount === 'string' ? parseInt(amount.replace(/\D/g, '')) : amount return new Intl.NumberFormat('id-ID').format(numAmount) } + const upsertAttachment = (attachments: string[], newId: string, index = 0) => { + if (attachments.length === 0) { + return [newId] + } + return attachments.map((id, i) => (i === index ? newId : id)) + } + + const handleUpload = async (file: File): Promise => { + return new Promise((resolve, reject) => { + const formData = new FormData() + formData.append('file', file) + formData.append('file_type', 'image') + formData.append('description', 'image purchase order payment') + + mutate(formData, { + onSuccess: r => { + setUploadedFileIds(prev => upsertAttachment(prev, r.id)) + setImageUrl(r.file_url) + resolve(r.id) + }, + onError: er => { + reject(er) + } + }) + }) + } + + const handleSubmit = async () => { + setIsSubmitting(true) + + try { + const requestData: SendPaymentPurchaseOrderRequest = { + reference: formData.referensi || undefined, + status: 'received', + attachment_file_ids: uploadedFileIds.length > 0 ? uploadedFileIds : undefined + } + sendPaymentPurchaseOrder.mutate( + { + id: id as string, + payload: requestData + }, + { + onSuccess: () => { + // Reset form after successful submission + setFormData(prev => ({ + ...prev, + referensi: '' + })) + setUploadedFileIds([]) + setExpanded(false) + } + } + ) + } catch (error) { + console.error('Error submitting payment:', error) + // Handle error (you might want to show a toast or error message) + } finally { + setIsSubmitting(false) + } + } + return ( { {/* Left Column */} - {/* Total Dibayar */} + {/* Total Dibayar - DISABLED */} { } value={formData.totalDibayar} onChange={handleChange('totalDibayar')} + disabled sx={{ '& .MuiInputBase-root': { textAlign: 'right' @@ -148,26 +175,6 @@ const PurchaseDetailSendPayment: React.FC = () => { /> - {/* Tgl. Transaksi */} - - - * Tgl. Transaksi - - } - type='date' - value={formData.tglTransaksi.split('/').reverse().join('-')} - onChange={handleChange('tglTransaksi')} - slotProps={{ - input: { - endAdornment: - } - }} - /> - - {/* Referensi */} @@ -185,6 +192,7 @@ const PurchaseDetailSendPayment: React.FC = () => { placeholder='Referensi' value={formData.referensi} onChange={handleChange('referensi')} + disabled={loading || isSubmitting} /> @@ -192,6 +200,7 @@ const PurchaseDetailSendPayment: React.FC = () => { { }} > - Attachment + Attachment {uploadedFileIds.length > 0 && `(${uploadedFileIds.length})`} - - Drag and drop files here or click to upload - + {/* Right Column */} - {/* Nomor */} + {/* Nomor - DISABLED */} @@ -246,140 +259,9 @@ const PurchaseDetailSendPayment: React.FC = () => { - - {/* Dibayar Dari */} - - - * Dibayar Dari - - option.label || ''} - value={dibayarDariOptions.find(option => option.value === formData.dibayarDari) || null} - onChange={(_, value: { label: string; value: string } | null) => handleDibayarDariChange(value)} - renderInput={(params: any) => } - noOptionsText='Tidak ada pilihan' - /> - - - {/* Empty space to match Referensi height */} - {/* Empty space */} - - {/* Pemotongan Button - aligned with Attachment */} - - - - {/* Pemotongan Items */} - {pemotonganItems.length > 0 && ( - - {pemotonganItems.map((item, index) => ( - - - - removePemotongan(item.id)} - sx={{ - backgroundColor: '#fff', - border: '1px solid #f44336', - '&:hover': { backgroundColor: '#ffebee' } - }} - > - - - - - - option.label || ''} - value={pemotonganOptions.find(option => option.value === item.dipotong) || null} - onChange={(_, value: { label: string; value: string } | null) => - updatePemotongan(item.id, 'dipotong', value?.value || '') - } - renderInput={(params: any) => } - noOptionsText='Tidak ada pilihan' - /> - - - - updatePemotongan(item.id, 'persentase', e.target.value)} - placeholder='0' - sx={{ - '& .MuiInputBase-root': { - textAlign: 'center' - } - }} - /> - - - - - - - - - - - - - {formatCurrency(calculatePemotongan(item))} - - - - - - ))} - - )} - {/* Bottom Section */} @@ -398,13 +280,15 @@ const PurchaseDetailSendPayment: React.FC = () => { variant='contained' startIcon={} fullWidth + onClick={handleSubmit} + disabled={loading || isSubmitting} sx={{ py: 1.5, textTransform: 'none', fontWeight: 'medium' }} > - Tambah Pembayaran + {isSubmitting ? 'Mengirim...' : 'Tambah Pembayaran'}