import { useState } from 'react' import { useMutation, useQueryClient } from '@tanstack/react-query' import { useAuth } from '../App' interface LineItem { id: number product_code: string | null description: string | null quantity: number | null unit_price: number | null amount: number | null } interface CreateDisputeModalProps { invoiceId: number invoiceNumber: string | null supplierId: number | null supplierName: string | null lineItems?: LineItem[] onClose: () => void onSuccess?: () => void } interface DisputeLineItemInput { invoice_line_item_id?: number product_name: string product_code?: string quantity_ordered?: number quantity_received?: number unit_price_quoted?: number unit_price_charged?: number total_charged: number total_expected?: number notes?: string } interface CreateDisputeRequest { invoice_id: number dispute_type: string priority: string title: string description?: string disputed_amount: number expected_amount?: number line_items?: DisputeLineItemInput[] tags?: string[] } export default function CreateDisputeModal({ invoiceId, invoiceNumber, supplierId: _supplierId, supplierName, lineItems = [], onClose, onSuccess, }: CreateDisputeModalProps) { const { token } = useAuth() const queryClient = useQueryClient() const [disputeType, setDisputeType] = useState('price_discrepancy') const [priority, setPriority] = useState('medium') const [title, setTitle] = useState('') const [description, setDescription] = useState('') const [disputedAmount, setDisputedAmount] = useState('') const [expectedAmount, setExpectedAmount] = useState('') const [selectedLineItems, setSelectedLineItems] = useState>(new Set()) const [lineItemSearch, setLineItemSearch] = useState('') const createMutation = useMutation({ mutationFn: async (data: CreateDisputeRequest) => { const res = await fetch('/api/disputes', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify(data), }) if (!res.ok) { const error = await res.json() throw new Error(error.detail || 'Failed to create dispute') } return res.json() }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['disputes'] }) queryClient.invalidateQueries({ queryKey: ['dispute-stats'] }) queryClient.invalidateQueries({ queryKey: ['invoice', invoiceId] }) if (onSuccess) onSuccess() onClose() }, }) const handleSubmit = (e: React.FormEvent) => { e.preventDefault() if (!title.trim()) { alert('Please enter a dispute title') return } if (!disputedAmount) { alert('Please enter a disputed amount') return } const data: CreateDisputeRequest = { invoice_id: invoiceId, dispute_type: disputeType, priority, title: title.trim(), disputed_amount: parseFloat(disputedAmount), } if (description.trim()) data.description = description.trim() if (expectedAmount) data.expected_amount = parseFloat(expectedAmount) // Convert selected line items to DisputeLineItemInput format if (selectedLineItems.size > 0) { data.line_items = Array.from(selectedLineItems) .map(itemId => { const item = lineItems.find(li => li.id === itemId) if (!item) return null const lineItem: DisputeLineItemInput = { invoice_line_item_id: item.id, product_name: item.description || item.product_code || 'Unnamed item', product_code: item.product_code || undefined, quantity_ordered: item.quantity || undefined, unit_price_charged: item.unit_price || undefined, total_charged: item.amount || 0, } return lineItem }) .filter((item): item is DisputeLineItemInput => item !== null) } createMutation.mutate(data) } const toggleLineItem = (lineItemId: number) => { const newSet = new Set(selectedLineItems) if (newSet.has(lineItemId)) { newSet.delete(lineItemId) } else { newSet.add(lineItemId) } setSelectedLineItems(newSet) // Auto-calculate disputed amount from selected items const selectedItems = lineItems.filter(item => newSet.has(item.id)) const total = selectedItems.reduce((sum, item) => sum + (item.amount || 0), 0) if (total > 0) { setDisputedAmount(total.toFixed(2)) } else if (newSet.size === 0) { // Clear disputed amount if no items selected setDisputedAmount('') } } // Filter line items based on search const filteredLineItems = lineItems.filter(item => { if (!lineItemSearch) return true const searchLower = lineItemSearch.toLowerCase() const description = (item.description || '').toLowerCase() const productCode = (item.product_code || '').toLowerCase() return description.includes(searchLower) || productCode.includes(searchLower) }) return (
e.stopPropagation()}>

Create Dispute

#{invoiceNumber || invoiceId} {supplierName && - {supplierName}}
setTitle(e.target.value)} style={styles.input} placeholder="Brief summary of the issue" required maxLength={200} />