diff --git a/src/hooks/useWorkorders.js b/src/hooks/useWorkorders.js index b65f4a8..0e835d7 100644 --- a/src/hooks/useWorkorders.js +++ b/src/hooks/useWorkorders.js @@ -117,22 +117,19 @@ export function useWorkorders(filters = {}) { queries.push(Query.limit(activeFilters.limit)) } + // Appwrite Query.equal mit Array = OR-Match. Serverseitig filtern, damit + // das Limit erst NACH dem Statusfilter greift (sonst wuerden archivierte + // Tickets die neuesten Slots belegen und aktive verdraengen). if (activeFilters.status && activeFilters.status.length > 0) { - if (activeFilters.status.length === 1) { - queries.push(Query.equal('status', activeFilters.status[0])) - } + queries.push(Query.equal('status', activeFilters.status)) } - + if (activeFilters.type && activeFilters.type.length > 0) { - if (activeFilters.type.length === 1) { - queries.push(Query.equal('type', activeFilters.type[0])) - } + queries.push(Query.equal('type', activeFilters.type)) } - + if (activeFilters.priority && activeFilters.priority.length > 0) { - if (activeFilters.priority.length === 1) { - queries.push(Query.equal('priority', activeFilters.priority[0])) - } + queries.push(Query.equal('priority', activeFilters.priority)) } if (activeFilters.customerId) { @@ -156,23 +153,8 @@ export function useWorkorders(filters = {}) { COLLECTIONS.WORKORDERS, queries ) - - // Clientseitige Filterung für Arrays (da Query.or() nicht verfügbar ist) - let filteredDocs = response.documents - - if (activeFilters.status && activeFilters.status.length > 1) { - filteredDocs = filteredDocs.filter(doc => activeFilters.status.includes(doc.status)) - } - - if (activeFilters.type && activeFilters.type.length > 1) { - filteredDocs = filteredDocs.filter(doc => activeFilters.type.includes(doc.type)) - } - - if (activeFilters.priority && activeFilters.priority.length > 1) { - filteredDocs = filteredDocs.filter(doc => activeFilters.priority.includes(doc.priority)) - } - - setWorkorders(filteredDocs) + + setWorkorders(response.documents) setError(null) } catch (err) { let errorMessage = err.message || 'Fehler beim Laden der Tickets' diff --git a/src/lib/leads.js b/src/lib/leads.js index eea4a0d..0625bd3 100644 --- a/src/lib/leads.js +++ b/src/lib/leads.js @@ -13,6 +13,19 @@ export function customerStage(c) { return 'customer' // Bestandsdaten ohne Status gelten als fester Kunde } +/** Archiviert = aus der Standardansicht ausgeblendet (orthogonal zur CRM-Stufe). */ +export function isArchived(c) { + return Boolean(c?.archived) +} + +/** Kunde archivieren / wiederherstellen. */ +export async function setCustomerArchived(customer, archived) { + return databases.updateDocument(DATABASE_ID, COLLECTIONS.CUSTOMERS, customer.$id, { + archived: Boolean(archived), + updatedAt: new Date().toISOString(), + }) +} + function ninjaPayload(c) { return { name: c.companyName || c.name, diff --git a/src/pages/CustomerDetailPage.jsx b/src/pages/CustomerDetailPage.jsx index 578325b..743172e 100644 --- a/src/pages/CustomerDetailPage.jsx +++ b/src/pages/CustomerDetailPage.jsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from 'react' import { useParams, Link } from 'react-router-dom' -import { FaSpinner, FaStar, FaArrowLeft, FaFloppyDisk, FaArrowUp, FaKey, FaArrowUpRightFromSquare } from 'react-icons/fa6' +import { FaSpinner, FaStar, FaArrowLeft, FaFloppyDisk, FaArrowUp, FaKey, FaArrowUpRightFromSquare, FaBoxArchive, FaBoxOpen } from 'react-icons/fa6' import { databases, DATABASE_ID, COLLECTIONS, Query } from '../lib/appwrite' import { PageHeader, Card, Button, Badge, Tabs, EmptyState, Field } from '../components/ui' import { StatusPill, PriorityPill } from '../components/ui/StatusPill' @@ -10,7 +10,7 @@ import AssignedProjectCard from '../components/AssignedProjectCard' import CopyableCredential, { getCustomerPortalPassword } from '../components/CopyableCredential' import { updateCustomerWithPortalAccess } from '../lib/customerAdminApi' import { useWebsiteProjects } from '../hooks/useWebsiteProjects' -import { customerStage, isInvoiceReady, upgradeToCustomer, syncCustomerToNinja } from '../lib/leads' +import { customerStage, isArchived, isInvoiceReady, setCustomerArchived, upgradeToCustomer, syncCustomerToNinja } from '../lib/leads' const PORTAL_URL = 'https://project.webklar.com' @@ -47,8 +47,14 @@ export default function CustomerDetailPage() { if (error || !customer) return
Zurueck} />
const stage = customerStage(customer) + const archived = isArchived(customer) const syntheticTicket = { customerId: customer.$id, customerName: customer.name, woid: '' } + const toggleArchived = async () => { + await setCustomerArchived(customer, !archived) + await load() + } + return (
Lead} {stage === 'customer' && Fester Kunde} {stage === 'lost' && Abgesagt} + {archived && Archiviert} } subtitle={[customer.location, customer.email, customer.phone].filter(Boolean).join(' � ')} - actions={} + actions={ + <> + + + + } /> diff --git a/src/pages/CustomersPage.jsx b/src/pages/CustomersPage.jsx index af56ce2..942ba31 100644 --- a/src/pages/CustomersPage.jsx +++ b/src/pages/CustomersPage.jsx @@ -3,19 +3,23 @@ import { Link } from 'react-router-dom' import { FaSpinner, FaPlus, FaStar, FaFileInvoiceDollar, FaFileLines } from 'react-icons/fa6' import { useCustomers } from '../hooks/useCustomers' import { useAuth } from '../context/AuthContext' -import { createLead, customerStage } from '../lib/leads' +import { createLead, customerStage, isArchived } from '../lib/leads' import { PageHeader, Card, Button, Badge, EmptyState, Field } from '../components/ui' export default function CustomersPage() { const { customers, loading, refresh } = useCustomers() const { user } = useAuth() const [search, setSearch] = useState('') - const [stage, setStage] = useState('all') // all | lead | customer + const [stage, setStage] = useState('all') // all | lead | customer | archived const [showCreate, setShowCreate] = useState(false) const filtered = useMemo(() => { return (customers || []) .filter((c) => { + const archived = isArchived(c) + // Archivierte nur im Archiviert-Tab, sonst nie + if (stage === 'archived') return archived + if (archived) return false const st = customerStage(c) if (st === 'lost') return stage === 'all' if (stage === 'all') return true @@ -56,6 +60,7 @@ export default function CustomersPage() { +
setSearch(e.target.value)} style={{ maxWidth: 320 }} /> @@ -77,11 +82,12 @@ export default function CustomersPage() {
{c.importantCustomer && } {c.name} - {c.code && · {c.code}} + {c.code && � {c.code}}
-
{[c.location, c.email].filter(Boolean).join(' · ') || '-'}
+
{[c.location, c.email].filter(Boolean).join(' � ') || '-'}
+ {isArchived(c) && Archiviert} {st === 'lead' && Lead} {st === 'customer' && Fester Kunde} {st === 'lost' && Abgesagt} diff --git a/src/pages/TicketsPage.jsx b/src/pages/TicketsPage.jsx index efb8f11..2615c5d 100644 --- a/src/pages/TicketsPage.jsx +++ b/src/pages/TicketsPage.jsx @@ -7,15 +7,28 @@ import CreateTicketModal from '../components/CreateTicketModal' import QuickOverviewModal from '../components/QuickOverviewModal' import { PageHeader, Card, Button, EmptyState } from '../components/ui' -const STATUS_PRESETS = ['Open', 'Occupied', 'Assigned', 'Awaiting', 'Added Info'] +// Alle Ticket-Status; "Closed" gilt als archiviert und ist standardmaessig ausgeblendet +const ALL_STATUSES = [ + 'Open', 'Occupied', 'Assigned', 'Awaiting', 'Added Info', + 'In Test', 'Halted', 'Aborted', 'Cancelled', 'Closed', +] +const ARCHIVED_STATUSES = ['Closed'] +const DEFAULT_STATUSES = ALL_STATUSES.filter((s) => !ARCHIVED_STATUSES.includes(s)) export default function TicketsPage() { const [limit, setLimit] = useState(10) - const [filters, setFilters] = useState({ status: STATUS_PRESETS, type: [], priority: [], limit: 10 }) + const [filters, setFilters] = useState({ status: DEFAULT_STATUSES, type: [], priority: [], limit: 10 }) + const [statusSel, setStatusSel] = useState(DEFAULT_STATUSES) const [localFilters, setLocalFilters] = useState({ woid: '', customer: '', userTopic: '', createdDate: '', type: '', system: '', priority: '', }) + const toggleStatus = (status) => { + setStatusSel((prev) => + prev.includes(status) ? prev.filter((s) => s !== status) : [...prev, status] + ) + } + const { workorders, loading, error, refresh, updateWorkorder, createWorkorder } = useWorkorders(filters) const { customers } = useCustomers() @@ -26,6 +39,7 @@ export default function TicketsPage() { const applyFilters = () => { setFilters((prev) => ({ ...prev, + status: statusSel.length ? statusSel : ALL_STATUSES, woid: localFilters.woid || undefined, customer: localFilters.customer || undefined, userTopic: localFilters.userTopic || undefined, @@ -92,6 +106,22 @@ export default function TicketsPage() {
+
+
+ Status + + +
+
+ {ALL_STATUSES.map((status) => ( + + ))} +
+