From ff999ae1b314550306b3c76f671d9d5f48cf5b93 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 11:32:45 +0000 Subject: [PATCH] Tickets: TicketCard, Dropdowns, statusClass und Altklassen (.tcard, .dropdown, .ticket-tab, .chevron) geloescht Co-Authored-By: Claude Sonnet 5.5 --- src/components/EditorDropdown.jsx | 37 ------ src/components/StatusDropdown.jsx | 22 ---- src/components/TicketCard.jsx | 179 ------------------------------ src/lib/ticketStatus.js | 14 --- src/styles/global.css | 70 +----------- 5 files changed, 1 insertion(+), 321 deletions(-) delete mode 100644 src/components/EditorDropdown.jsx delete mode 100644 src/components/StatusDropdown.jsx delete mode 100644 src/components/TicketCard.jsx diff --git a/src/components/EditorDropdown.jsx b/src/components/EditorDropdown.jsx deleted file mode 100644 index e10af5b..0000000 --- a/src/components/EditorDropdown.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import { FaUserPlus } from 'react-icons/fa6' -import { useEmployees } from '../hooks/useEmployees' - -export default function EditorDropdown({ value, onChange }) { - const { employees } = useEmployees() - - // Finde den zugewiesenen Mitarbeiter anhand der userId - const assignedEmployee = employees.find(emp => emp.userId === value) - - // Zeige Kürzel wenn zugewiesen, sonst Icon - const displayValue = assignedEmployee?.shortcode || - - return ( -
- -
- onChange('')} - > - (Unassigned) - - {employees.map(employee => ( - onChange(employee.userId)} - > - {employee.displayName}{employee.shortcode ? ` (${employee.shortcode})` : ''} - - ))} -
-
- ) -} diff --git a/src/components/StatusDropdown.jsx b/src/components/StatusDropdown.jsx deleted file mode 100644 index 0e95787..0000000 --- a/src/components/StatusDropdown.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import { TICKET_STATUSES, statusLabel } from '../lib/ticketStatus' - -export default function StatusDropdown({ value, onChange }) { - return ( -
- -
- {TICKET_STATUSES.map(status => ( - onChange(status.value)} - > - {status.label} - - ))} -
-
- ) -} diff --git a/src/components/TicketCard.jsx b/src/components/TicketCard.jsx deleted file mode 100644 index 2d7fb42..0000000 --- a/src/components/TicketCard.jsx +++ /dev/null @@ -1,179 +0,0 @@ -import { useState } from 'react' -import { FaChevronDown, FaPlay, FaStop, FaPlus } from 'react-icons/fa6' -import { formatDistanceToNow, format } from 'date-fns' -import { de } from 'date-fns/locale' -import StatusDropdown from './StatusDropdown' -import UrgentToggle from './UrgentToggle' -import EditorDropdown from './EditorDropdown' -import CreateWorksheetModal from './CreateWorksheetModal' -import WorksheetList from './WorksheetList' -import WorksheetTimesModal from './WorksheetTimesModal' -import OwnSupportModal from './OwnSupportModal' -import WorksheetSummary from './WorksheetSummary' -import TicketProjects from './TicketProjects' -import ProjectContextPanel from './ProjectContextPanel' -import Tabs from './ui/Tabs' -import Badge from './ui/Badge' -import { StatusPill, UrgentPill } from './ui/StatusPill' -import { useWorksheets } from '../hooks/useWorksheets' -import { useCustomerAbo } from '../hooks/useCustomerAbo' -import { useAuth } from '../context/AuthContext' -import { databases, DATABASE_ID, COLLECTIONS } from '../lib/appwrite' -import { canEditWorksheetTimes } from '../lib/ticketForm' -import { canAddOwnSupport } from '../lib/support' -import { ticketTypeLabel } from '../lib/ticketStatus' - -// Projekt / Preview steht mit im Details-Reiter -const TABS = [ - { id: 'details', label: 'Details' }, - { id: 'worksheets', label: 'Arbeitsblätter' }, -] - -export default function TicketCard({ ticket, onUpdate }) { - const { user, employee } = useAuth() - const [expanded, setExpanded] = useState(false) - const [activeTab, setActiveTab] = useState('details') - const [showCreateWorksheet, setShowCreateWorksheet] = useState(false) - const [editTimesWorksheet, setEditTimesWorksheet] = useState(null) - const [ownSupportWorksheet, setOwnSupportWorksheet] = useState(null) - // Abo-Stundenkonto gibt es nur bei Wartungs-Abo; erst laden, wenn Arbeitsblaetter gebraucht werden - const { hoursAllowed: aboHoursAllowed } = useCustomerAbo( - ticket.customerId, - (expanded && activeTab === 'worksheets') || showCreateWorksheet - ) - - const { worksheets, loading: worksheetsLoading, createWorksheet, updateWorksheet, updateWorksheetTimes, saveOwnSupport, getTotalTime } = useWorksheets( - expanded ? ticket.woid : null - ) - - const createdAt = new Date(ticket.$createdAt || ticket.createdAt) - const elapsed = formatDistanceToNow(createdAt, { locale: de, addSuffix: true }) - - const stop = (e) => e.stopPropagation() - - // Abo-Stunden: Arbeitszeit vom Stundenguthaben des Wartungs-Abos abziehen oder nicht - const handleTogglePlan = (ws) => updateWorksheet(ws.$id, { countsToPlan: !ws.countsToPlan }) - - const handleCreateWorksheet = async (worksheetData, currentUser) => { - const result = await createWorksheet(worksheetData, currentUser) - if (result.success && worksheetData.newStatus !== ticket.status) { - await onUpdate(ticket.$id, { status: worksheetData.newStatus }) - } - // Akquise-Automatik: Ergebnis im Worksheet steuert den Kundenstatus - if (result.success && ticket.type === 'Akquise' && ticket.customerId) { - const outcome = worksheetData.newStatus - const next = outcome === 'Zugesagt' ? 'customer' : outcome === 'Abgesagt' ? 'lost' : null - if (next) { - try { - await databases.updateDocument(DATABASE_ID, COLLECTIONS.CUSTOMERS, ticket.customerId, { - customerStatus: next, - updatedAt: new Date().toISOString(), - }) - } catch { /* nicht kritisch */ } - } - } - return result - } - - return ( -
-
setExpanded((e) => !e)}> -
- {ticket.woid || ticket.$id?.slice(-5)} - {elapsed} -
- -
-
- {ticket.customerName || 'Unbekannt'} - {ticket.customerLocation && · {ticket.customerLocation}} -
-
{ticket.topic || ticket.title || 'Kein Betreff'}
-
- - - {ticket.type && {ticketTypeLabel(ticket.type)}} -
-
- -
-
- onUpdate(ticket.$id, { status: v })} /> -
- onUpdate(ticket.$id, { priority: v })} /> - setExpanded((e) => !e)} style={{ cursor: 'pointer' }} /> -
-
- - {expanded && ( -
-
- {ticket.startDate || format(createdAt, 'dd.MM.yyyy')} - {ticket.deadline || '-'} - {ticket.requestedBy && Angefragt von: {ticket.requestedBy}} - -
onUpdate(ticket.$id, { assignedTo: v })} />
-
- - - -
- {activeTab === 'details' && ( -
-
-
Ticket-Beschreibung
-

- {ticket.details || 'Keine Details vorhanden.'} -

-
- - -
- )} - - {activeTab === 'worksheets' && ( -
-
- -
- - canEditWorksheetTimes(ws, user, employee)} - onEditTimes={setEditTimesWorksheet} - canAddSupport={(ws) => canAddOwnSupport(ws, user, employee)} - onOwnSupport={setOwnSupportWorksheet} - currentUserId={user?.$id} - onTogglePlan={handleTogglePlan} - aboHoursAllowed={aboHoursAllowed} - /> -
- )} -
-
- )} - - setShowCreateWorksheet(false)} workorder={ticket} onCreate={handleCreateWorksheet} aboHoursAllowed={aboHoursAllowed} /> - {ownSupportWorksheet && ( - setOwnSupportWorksheet(null)} - onSave={(ws, task) => saveOwnSupport(ws, task, user, employee)} - /> - )} - {editTimesWorksheet && ( - setEditTimesWorksheet(null)} - onSave={(ws, times) => updateWorksheetTimes(ws, times, user, employee)} - /> - )} -
- ) -} diff --git a/src/lib/ticketStatus.js b/src/lib/ticketStatus.js index 0ca3fca..216c9f9 100644 --- a/src/lib/ticketStatus.js +++ b/src/lib/ticketStatus.js @@ -74,20 +74,6 @@ export function statusValuesFor(groupIds) { return STATUS_GROUPS.filter((group) => groupIds.includes(group.id)).flatMap((group) => group.values) } -const GROUP_CLASS = { - open: 'status-open', - working: 'status-working', - waiting: 'status-awaiting', - review: 'status-assigned', - done: 'status-closed', - cancelled: 'status-closed', -} - -// CSS-Klasse fuer das Status-Abzeichen -export function statusClass(status) { - return GROUP_CLASS[statusGroup(status || 'Open')] || 'status-open' -} - // Statt fuenf Prioritaeten gibt es nur noch "dringend" oder nicht. // Frueher gespeicherte Stufen: Hoch (3) und Kritisch (4) gelten als dringend. export const NORMAL_PRIORITY = 2 diff --git a/src/styles/global.css b/src/styles/global.css index 68ee081..c00e7a1 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -523,46 +523,6 @@ a.list-row:hover .cell-title { color: var(--accent-ink); } .page-subtitle { color: var(--text-muted); font-size: 14px; margin-top: 4px; } .page-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; } -/* --- Ticket Card (ersetzt Tabelle) --- */ -.tickets-list { display: flex; flex-direction: column; gap: var(--sp-3); } -.tcard { - background: var(--surface-1); border: 1px solid var(--border); - border-radius: var(--r-md); overflow: visible; transition: border-color 0.15s ease; -} -.tcard:hover { border-color: var(--border-strong); } -.tcard.open { border-color: var(--border-accent); } -.tcard-detail { border-radius: 0 0 var(--r-md) var(--r-md); } -.tcard-main { - display: grid; grid-template-columns: 92px 1fr auto; gap: var(--sp-4); - padding: var(--sp-4); align-items: center; cursor: pointer; -} -.tcard-woid { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; } -.tcard-woid .id { font-weight: 800; font-size: 15px; color: var(--accent); } -.tcard-woid .age { font-size: 11px; color: var(--text-faint); } -.tcard-body { min-width: 0; } -.tcard-row1 { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; margin-bottom: 4px; } -.tcard-customer { font-weight: 700; } -.tcard-loc { color: var(--text-muted); font-size: 13px; } -.tcard-topic { color: var(--text-muted); font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.tcard-badges { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px; } - -/* Eine Zeile ueber der Arbeitsblatt-Liste */ -.worksheet-summary { - display: flex; flex-wrap: wrap; gap: 6px 20px; align-items: baseline; - padding: 10px 14px; margin-bottom: 16px; font-size: 14px; color: var(--text-muted); - background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-md); -} -.worksheet-summary strong { color: var(--text); } -.worksheet-summary .open { color: var(--orange-fg); font-weight: 700; } -.tcard-side { display: flex; align-items: center; gap: var(--sp-3); } -.tcard-detail { border-top: 1px solid var(--border); padding: var(--sp-4); background: var(--surface-1); } -.chevron { color: var(--text-muted); transition: transform 0.2s ease; } -.chevron.up { transform: rotate(180deg); } -@media (max-width: 720px) { - .tcard-main { grid-template-columns: 64px 1fr; } - .tcard-side { grid-column: 1 / -1; justify-content: space-between; } -} - /* --- Generic data list rows (Kunden/Finanzen/Dokumente) --- */ .list { display: flex; flex-direction: column; } @@ -600,18 +560,6 @@ a.list-row:hover .cell-title { color: var(--accent-ink); } /* Planboard (bis P3): die 7-Spalten-Woche scrollt waagerecht im Container, nicht die Seite */ .planboard-scroll { overflow-x: auto; } -.automatic-assignment-note { - padding: 12px 14px; - border-left: 2px solid var(--accent); - background: var(--surface-2); -} -.automatic-assignment-note p { - margin: 5px 0 0; - color: var(--text-muted); - font-size: 13px; - line-height: 1.45; -} - /* Neues Ticket: Datum + Uhrzeit nebeneinander (heller Kalender, kein color-scheme: dark) */ .datetime-pair { display: flex; gap: 8px; } .datetime-pair .form-control, .datetime-pair .input { flex: 1; min-width: 0; } @@ -1134,13 +1082,6 @@ select.form-control { } .overlay-close:hover { color: var(--text); background: var(--bg-hover); } -/* Reiter (Tabs.jsx): Unterstrich statt Kasten wie .tabs / .tab */ -.ticket-tabs { display: flex; gap: 24px; margin-bottom: 16px; border-bottom: 1px solid var(--line); overflow-x: auto; scrollbar-width: none; } -.ticket-tab { margin-bottom: -1px; padding: 8px 0 10px; border-bottom: 2px solid transparent; color: var(--text-2); font-weight: 500; white-space: nowrap; } -.ticket-tab:hover { color: var(--text); } -.ticket-tab-active, .ticket-tab-active:hover { border-bottom-color: var(--accent); color: var(--accent-ink); font-weight: 600; } -.ticket-tab-content { min-height: 120px; } - /* Umschalter */ .seg button.active { background: var(--bg); color: var(--text); box-shadow: 0 1px 2px rgba(26,31,54,.12), 0 0 0 1px rgba(26,31,54,.04); } @@ -1156,7 +1097,7 @@ select.form-control { border-radius: var(--radius); background: var(--grey-bg); color: var(--grey-fg); font-weight: 600; } -/* Neutral: Karte, Raster, Aufklappmenue */ +/* Neutral: Karte, Raster */ .card { margin-bottom: 16px; background: none; border: 0; border-radius: 0; color: var(--text); } .card-header { padding: 0 0 8px; background: none; border-bottom: 1px solid var(--line); color: var(--text); font-weight: 600; } .card-body { padding: 12px 0; color: var(--text); } @@ -1164,15 +1105,6 @@ div.row { display: flex; flex-wrap: wrap; margin: 0 -8px; } .col { padding: 0 8px; } .col-6 { width: 50%; } @media (max-width: 768px) { .col-6 { width: 100%; } } -.dropdown { position: relative; display: inline-block; width: 100%; text-align: center; } -.dropdown .btn { width: 100%; padding: 0 8px; font-size: 12px; box-shadow: none; } -.dropdown-content { - display: none; position: absolute; left: 50%; transform: translateX(-50%); z-index: 100; min-width: 160px; - background: var(--bg); border-radius: var(--radius); box-shadow: var(--shadow-pop); text-align: left; -} -.dropdown:hover .dropdown-content { display: block; } -.dropdown-item { display: block; padding: 8px 16px; color: var(--text); cursor: pointer; } -.dropdown-item:hover { background: var(--bg-hover); } /* Laden: neutraler grauer Ring */ .spinner { width: 40px; height: 40px; margin: 0 auto; border: 4px solid var(--line); border-top-color: var(--text-2); border-radius: 50%; animation: spin 1s linear infinite; }