356 lines
13 KiB
JavaScript
356 lines
13 KiB
JavaScript
import { useState } from 'react'
|
|
import { FaLock, FaLockOpen, FaPlay, FaStop, FaTruck, FaSackDollar, FaUserGear, FaPlus, FaClockRotateLeft } from 'react-icons/fa6'
|
|
import { formatDistanceToNow, format } from 'date-fns'
|
|
import { de } from 'date-fns/locale'
|
|
import StatusDropdown from './StatusDropdown'
|
|
import PriorityDropdown from './PriorityDropdown'
|
|
import EditorDropdown from './EditorDropdown'
|
|
import ResponseDropdown from './ResponseDropdown'
|
|
import CreateWorksheetModal from './CreateWorksheetModal'
|
|
import StatusHistoryModal from './StatusHistoryModal'
|
|
import WorksheetList from './WorksheetList'
|
|
import WorksheetStats from './WorksheetStats'
|
|
import { useWorksheets } from '../hooks/useWorksheets'
|
|
|
|
const PRIORITY_CLASSES = {
|
|
0: 'priority-none',
|
|
1: 'priority-low',
|
|
2: 'priority-medium',
|
|
3: 'priority-high',
|
|
4: 'priority-critical'
|
|
}
|
|
|
|
const PRIORITY_LABELS = {
|
|
0: 'NONE',
|
|
1: 'LOW',
|
|
2: 'MEDIUM',
|
|
3: 'HIGH',
|
|
4: 'CRITICAL'
|
|
}
|
|
|
|
const STATUS_CLASSES = {
|
|
'Open': 'status-open',
|
|
'Occupied': 'status-occupied',
|
|
'Assigned': 'status-assigned',
|
|
'Awaiting': 'status-awaiting',
|
|
'Closed': 'status-closed'
|
|
}
|
|
|
|
const APPROVAL_ICONS = {
|
|
'pending': FaUserGear,
|
|
'approved': FaSackDollar,
|
|
'quote': FaSackDollar,
|
|
'shipping': FaTruck
|
|
}
|
|
|
|
export default function TicketRow({ ticket, onUpdate, onExpand }) {
|
|
const [expanded, setExpanded] = useState(false)
|
|
const [locked, setLocked] = useState(true)
|
|
const [showCreateWorksheet, setShowCreateWorksheet] = useState(false)
|
|
const [showHistoryModal, setShowHistoryModal] = useState(false)
|
|
|
|
// Worksheets für dieses Ticket laden (nur wenn expanded)
|
|
const {
|
|
worksheets,
|
|
loading: worksheetsLoading,
|
|
createWorksheet,
|
|
getTotalTime
|
|
} = useWorksheets(expanded ? ticket.woid : null)
|
|
|
|
const createdAt = new Date(ticket.$createdAt || ticket.createdAt)
|
|
const elapsed = formatDistanceToNow(createdAt, { locale: de })
|
|
|
|
const handleStatusChange = (newStatus) => {
|
|
onUpdate(ticket.$id, { status: newStatus })
|
|
}
|
|
|
|
const handlePriorityChange = (newPriority) => {
|
|
onUpdate(ticket.$id, { priority: newPriority })
|
|
}
|
|
|
|
const handleEditorChange = (newEditor) => {
|
|
onUpdate(ticket.$id, { assignedTo: newEditor })
|
|
}
|
|
|
|
const handleResponseChange = (newResponse) => {
|
|
onUpdate(ticket.$id, { responseLevel: newResponse })
|
|
}
|
|
|
|
const toggleLock = () => {
|
|
if (locked) {
|
|
setExpanded(true)
|
|
onExpand?.(ticket.$id)
|
|
} else {
|
|
setExpanded(false)
|
|
}
|
|
setLocked(!locked)
|
|
}
|
|
|
|
const handleCreateWorksheet = async (worksheetData, currentUser) => {
|
|
const result = await createWorksheet(worksheetData, currentUser)
|
|
|
|
// Wenn Status geändert wurde, aktualisiere Work Order
|
|
if (result.success && worksheetData.newStatus !== ticket.status) {
|
|
await onUpdate(ticket.$id, {
|
|
status: worksheetData.newStatus,
|
|
responseLevel: worksheetData.newResponseLevel || ticket.responseLevel
|
|
})
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
const ApprovalIcon = APPROVAL_ICONS[ticket.approvalStatus] || FaUserGear
|
|
|
|
return (
|
|
<>
|
|
<tr className={`ticket-row ${expanded ? 'ticket-expanded' : 'ticket-collapsed'}`}>
|
|
<td className={`ticket-id ${expanded ? 'ticket-id-expanded' : ''}`} rowSpan={2}>
|
|
<div><strong>WOID:</strong> {ticket.woid || ticket.$id?.slice(-5)}</div>
|
|
<div className="ticket-time">{elapsed}</div>
|
|
</td>
|
|
<td className="ticket-info" rowSpan={2}>
|
|
<strong>{format(createdAt, 'dd.MM.yyyy')}</strong>
|
|
</td>
|
|
<td className="ticket-info" rowSpan={2}>
|
|
<strong>{ticket.type}</strong>
|
|
<br />
|
|
{ticket.serviceType || 'Remote'}
|
|
<br />
|
|
<FaPlay className="text-green" /> {ticket.startDate || format(createdAt, 'dd.MM.yyyy')}
|
|
<br />
|
|
<FaStop className="text-red" /> {ticket.deadline || '-'}
|
|
</td>
|
|
<td className="ticket-info" rowSpan={2}>
|
|
<strong>{ticket.systemType || 'n/a'}</strong>
|
|
</td>
|
|
<td rowSpan={2}>
|
|
<strong>{ticket.customerName || 'Unknown'}</strong>
|
|
<br />
|
|
{ticket.customerLocation || ''}
|
|
</td>
|
|
<td rowSpan={2}>
|
|
<strong>{ticket.requestedBy || '-'}</strong>
|
|
<br />
|
|
{ticket.requestedFor && <>Requested for: {ticket.requestedFor}<br /></>}
|
|
{ticket.topic}
|
|
</td>
|
|
<td className={`text-center ${STATUS_CLASSES[ticket.status] || 'status-open'}`}>
|
|
<StatusDropdown
|
|
value={ticket.status}
|
|
onChange={handleStatusChange}
|
|
/>
|
|
</td>
|
|
<td className={`text-center ${PRIORITY_CLASSES[ticket.priority] || 'priority-low'}`}>
|
|
<PriorityDropdown
|
|
value={ticket.priority}
|
|
onChange={handlePriorityChange}
|
|
/>
|
|
</td>
|
|
<td
|
|
className={`text-center ${ticket.approvalStatus === 'approved' ? 'bg-green' : 'bg-yellow'}`}
|
|
rowSpan={2}
|
|
style={{ verticalAlign: 'middle' }}
|
|
>
|
|
<ApprovalIcon size={24} />
|
|
</td>
|
|
<td
|
|
className="bg-dark-grey text-center text-white"
|
|
rowSpan={2}
|
|
style={{ verticalAlign: 'middle', cursor: 'pointer' }}
|
|
onClick={toggleLock}
|
|
>
|
|
{locked ? <FaLock size={24} /> : <FaLockOpen size={24} />}
|
|
</td>
|
|
</tr>
|
|
<tr className="ticket-row">
|
|
<td className={`text-center ${STATUS_CLASSES[ticket.status] || 'status-open'}`}>
|
|
<EditorDropdown
|
|
value={ticket.assignedTo}
|
|
onChange={handleEditorChange}
|
|
/>
|
|
</td>
|
|
<td className={`text-center ${PRIORITY_CLASSES[ticket.priority] || 'priority-low'}`}>
|
|
<ResponseDropdown
|
|
value={ticket.responseLevel}
|
|
onChange={handleResponseChange}
|
|
/>
|
|
</td>
|
|
</tr>
|
|
{expanded && (
|
|
<>
|
|
<tr className="worksheet-expansion">
|
|
<td colSpan={10} className="worksheet-cell">
|
|
<div className="card" style={{
|
|
borderRadius: '0 0 12px 12px',
|
|
marginTop: 0,
|
|
border: '1px solid rgba(16, 185, 129, 0.2)',
|
|
borderTop: 'none'
|
|
}}>
|
|
<div className="card-body" style={{ borderRadius: '0 0 12px 12px', padding: '20px' }}>
|
|
{/* Bento Box Layout: 2 Spalten */}
|
|
<div style={{
|
|
display: 'grid',
|
|
gridTemplateColumns: '1fr 1fr',
|
|
gap: '20px',
|
|
alignItems: 'stretch'
|
|
}}>
|
|
{/* Linke Spalte: Ticket-Beschreibung (50%) */}
|
|
<div style={{
|
|
background: 'rgba(45, 55, 72, 0.5)',
|
|
borderRadius: '12px',
|
|
padding: '20px',
|
|
border: '1px solid rgba(16, 185, 129, 0.2)',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
height: '100%'
|
|
}}>
|
|
<h5 style={{
|
|
color: 'var(--dark-text)',
|
|
fontWeight: 'bold',
|
|
marginBottom: '16px',
|
|
fontSize: '18px',
|
|
flex: '0 0 auto'
|
|
}}>
|
|
📋 Ticket-Beschreibung
|
|
</h5>
|
|
<p style={{
|
|
fontSize: '14px',
|
|
lineHeight: '1.8',
|
|
color: 'rgba(226, 232, 240, 0.8)',
|
|
whiteSpace: 'pre-wrap',
|
|
margin: 0,
|
|
flex: '1 1 auto',
|
|
overflowY: 'auto'
|
|
}}>
|
|
{ticket.details || 'Keine Details vorhanden.'}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Rechte Spalte: Statistics, Buttons (50%) */}
|
|
<div style={{
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: '16px',
|
|
height: '100%'
|
|
}}>
|
|
{/* Button Row: Add Worksheet (100%) + History Icon Button */}
|
|
<div style={{
|
|
display: 'flex',
|
|
gap: '8px',
|
|
alignItems: 'stretch'
|
|
}}>
|
|
{/* Add Worksheet Button - 100% width minus icon button */}
|
|
<button
|
|
className="btn"
|
|
style={{
|
|
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
|
|
color: 'white',
|
|
border: 'none',
|
|
padding: '12px 20px',
|
|
borderRadius: '8px',
|
|
fontWeight: 'bold',
|
|
cursor: 'pointer',
|
|
transition: 'all 0.2s ease',
|
|
flex: 1
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.transform = 'translateY(-2px)'
|
|
e.currentTarget.style.boxShadow = '0 4px 12px rgba(16, 185, 129, 0.4)'
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.transform = 'translateY(0)'
|
|
e.currentTarget.style.boxShadow = 'none'
|
|
}}
|
|
onClick={() => setShowCreateWorksheet(true)}
|
|
>
|
|
<FaPlus style={{ marginRight: '8px' }} /> Add Worksheet
|
|
</button>
|
|
|
|
{/* History Icon Button - klein, grau, nur Icon */}
|
|
<button
|
|
style={{
|
|
background: '#616161',
|
|
color: 'white',
|
|
border: 'none',
|
|
padding: '12px',
|
|
borderRadius: '8px',
|
|
cursor: 'pointer',
|
|
transition: 'all 0.2s ease',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
minWidth: '44px',
|
|
width: '44px'
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.background = '#757575'
|
|
e.currentTarget.style.transform = 'translateY(-2px)'
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.background = '#616161'
|
|
e.currentTarget.style.transform = 'translateY(0)'
|
|
}}
|
|
onClick={() => setShowHistoryModal(true)}
|
|
title="Status History"
|
|
>
|
|
<FaClockRotateLeft size={18} />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Statistiken */}
|
|
{worksheets.length > 0 && (
|
|
<div style={{
|
|
background: 'rgba(45, 55, 72, 0.5)',
|
|
borderRadius: '12px',
|
|
padding: '16px',
|
|
border: '1px solid rgba(16, 185, 129, 0.2)',
|
|
flex: '1 1 auto',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
minHeight: 0
|
|
}}>
|
|
<WorksheetStats worksheets={worksheets} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Gesamtarbeitszeit und Worksheet-Liste - 100% Breite unter dem Bento Box */}
|
|
<div style={{
|
|
marginTop: '20px',
|
|
width: '100%'
|
|
}}>
|
|
<WorksheetList
|
|
worksheets={worksheets}
|
|
totalTime={getTotalTime()}
|
|
loading={worksheetsLoading}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</>
|
|
)}
|
|
|
|
<CreateWorksheetModal
|
|
isOpen={showCreateWorksheet}
|
|
onClose={() => setShowCreateWorksheet(false)}
|
|
workorder={ticket}
|
|
onCreate={handleCreateWorksheet}
|
|
/>
|
|
|
|
<StatusHistoryModal
|
|
isOpen={showHistoryModal}
|
|
onClose={() => setShowHistoryModal(false)}
|
|
worksheets={worksheets}
|
|
ticket={ticket}
|
|
/>
|
|
<tr className="spacer">
|
|
<td colSpan={10} style={{ height: '12px', background: 'transparent', border: 'none' }}></td>
|
|
</tr>
|
|
</>
|
|
)
|
|
}
|