'use client'; import { useState, useEffect } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Switch } from '@/components/ui/switch'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from '@/components/ui/alert-dialog'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Checkbox } from '@/components/ui/checkbox'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Server, Edit, Save, Power, Info, Cpu, RefreshCw, User, Receipt, Ticket as TicketIcon, Download } from 'lucide-react'; import { format } from 'date-fns'; import { ConfigurationItem } from '@/lib/types/autotask'; import { PurchaseHistoryModal } from './purchase-history-modal'; import { RelatedTicketsModal } from './related-tickets-modal'; interface PSATabProps { device?: ConfigurationItem; onUpdate: (device: ConfigurationItem) => void; } export function PSATab({ device, onUpdate }: PSATabProps) { const [editMode, setEditMode] = useState(false); const [saving, setSaving] = useState(false); const [editedData, setEditedData] = useState>(device || {}); const [error, setError] = useState(null); const [contactName, setContactName] = useState(null); const [loadingContact, setLoadingContact] = useState(false); const [purchaseHistoryOpen, setPurchaseHistoryOpen] = useState(false); const [relatedTicketsOpen, setRelatedTicketsOpen] = useState(false); const [exportModalOpen, setExportModalOpen] = useState(false); const [selectedFields, setSelectedFields] = useState>(new Set()); // Sync editedData when device prop changes useEffect(() => { if (device) { setEditedData(device); } }, [device]); // Fetch contact information if contactID exists useEffect(() => { if (!device?.contactID) { setContactName(null); return; } const fetchContact = async () => { setLoadingContact(true); try { const response = await fetch(`/api/contacts/${device.contactID}`); if (response.ok) { const data = await response.json(); setContactName(data.contact ? `${data.contact.firstName} ${data.contact.lastName}` : null); } } catch (err) { console.error('Failed to fetch contact:', err); } finally { setLoadingContact(false); } }; fetchContact(); }, [device?.contactID]); const handleSave = async () => { if (!device) return; setSaving(true); setError(null); try { const response = await fetch(`/api/configuration-items/${device.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(editedData), }); if (!response.ok) { throw new Error('Failed to update configuration item'); } const updated = await response.json(); onUpdate(updated.configurationItem); setEditMode(false); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to save changes'); } finally { setSaving(false); } }; const handleMakeInactive = async () => { if (!device) return; setSaving(true); setError(null); try { const response = await fetch(`/api/configuration-items/${device.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ isActive: false }), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || 'Failed to update configuration item'); } const updated = await response.json(); if (updated.configurationItem) { onUpdate(updated.configurationItem); setEditedData(updated.configurationItem); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to make inactive'); } finally { setSaving(false); } }; // Define available fields for export const availableFields = [ { key: 'id', label: 'ID' }, { key: 'referenceTitle', label: 'Reference Title' }, { key: 'referenceNumber', label: 'Reference Number' }, { key: 'serialNumber', label: 'Serial Number' }, { key: 'location', label: 'Location' }, { key: 'isActive', label: 'Active Status' }, { key: 'modelNumber', label: 'Model Number' }, { key: 'macAddress', label: 'MAC Address' }, { key: 'installDate', label: 'Install Date' }, { key: 'warrantyExpirationDate', label: 'Warranty Expiration' }, { key: 'rmmDeviceUID', label: 'RMM Device UID' }, { key: 'notes', label: 'Notes' }, { key: 'companyID', label: 'Company ID' }, { key: 'contactID', label: 'Contact ID' }, { key: 'contractID', label: 'Contract ID' }, { key: 'createDate', label: 'Create Date' }, { key: 'lastModifiedTime', label: 'Last Modified Time' }, { key: 'productID', label: 'Product ID' }, { key: 'vendorName', label: 'Vendor Name' }, { key: 'deviceNetworkingID', label: 'Device Networking ID' }, { key: 'numberOfUsers', label: 'Number of Users' }, { key: 'setupFee', label: 'Setup Fee' }, ]; const toggleField = (fieldKey: string) => { const newSelected = new Set(selectedFields); if (newSelected.has(fieldKey)) { newSelected.delete(fieldKey); } else { newSelected.add(fieldKey); } setSelectedFields(newSelected); }; const toggleAllFields = () => { if (selectedFields.size === availableFields.length) { setSelectedFields(new Set()); } else { setSelectedFields(new Set(availableFields.map(f => f.key))); } }; const handleExport = () => { if (!device || selectedFields.size === 0) return; // Build CSV header const headers = availableFields .filter(f => selectedFields.has(f.key)) .map(f => f.label); // Build CSV row const row = availableFields .filter(f => selectedFields.has(f.key)) .map(f => { const value = device[f.key as keyof ConfigurationItem]; // Format dates if ((f.key === 'installDate' || f.key === 'warrantyExpirationDate' || f.key === 'createDate' || f.key === 'lastModifiedTime') && value) { return format(new Date(value as string), 'yyyy-MM-dd HH:mm:ss'); } // Handle boolean if (typeof value === 'boolean') { return value ? 'Active' : 'Inactive'; } // Handle null/undefined if (value === null || value === undefined) { return ''; } // Escape quotes and wrap in quotes if contains comma or newline const stringValue = String(value); if (stringValue.includes(',') || stringValue.includes('\n') || stringValue.includes('"')) { return `"${stringValue.replace(/"/g, '""')}"`; } return stringValue; }); // Create CSV content const csvContent = [headers.join(','), row.join(',')].join('\n'); // Create and trigger download const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.setAttribute('href', url); link.setAttribute('download', `config-item-${device.id}-${format(new Date(), 'yyyy-MM-dd')}.csv`); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); setExportModalOpen(false); }; return ( <>
PSA Configuration Item
{!editMode ? ( <> {device?.isActive && ( Make Configuration Item Inactive? This will mark the configuration item as inactive in Autotask PSA. You can reactivate it later if needed. Cancel Make Inactive )} ) : ( <> )}
{error && (
{error}
)} {device ? (
{/* Basic Information */}

Basic Information

{editMode ? ( setEditedData({...editedData, referenceTitle: e.target.value})} className="mt-1" /> ) : (

{device.referenceTitle}

)}
{editMode ? ( setEditedData({...editedData, referenceNumber: e.target.value})} className="mt-1" /> ) : (

{device.referenceNumber || '-'}

)}
{editMode ? ( setEditedData({...editedData, serialNumber: e.target.value})} className="mt-1" /> ) : (

{device.serialNumber || '-'}

)}
{editMode ? ( setEditedData({...editedData, location: e.target.value})} className="mt-1" /> ) : (

{device.location || '-'}

)}
{editMode ? (
setEditedData({...editedData, isActive: checked})} />
) : (
{device.isActive ? ( Active ) : ( Inactive )}
)}
{loadingContact ? (

Loading...

) : contactName ? (
{contactName}
) : device.contactID ? (

Contact ID: {device.contactID}

) : (

No contact assigned

)}
{/* Technical Details */}

Technical Details

{editMode ? ( setEditedData({...editedData, modelNumber: e.target.value})} className="mt-1" /> ) : (

{device.modelNumber || '-'}

)}
{editMode ? ( setEditedData({...editedData, macAddress: e.target.value})} className="mt-1" /> ) : (

{device.macAddress || '-'}

)}

{device.installDate ? format(new Date(device.installDate), 'MMM d, yyyy') : '-'}

{device.warrantyExpirationDate ? format(new Date(device.warrantyExpirationDate), 'MMM d, yyyy') : '-'}

{device.rmmDeviceUID || '-'}

{/* Notes */}

Notes

{editMode ? (