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 */}
-
- }
- variant='text'
- color='primary'
- sx={{ textTransform: 'none' }}
- onClick={addPemotongan}
- >
- Pemotongan
-
-
- {/* 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'}