diff --git a/packages/frontend/src/crm/api.ts b/packages/frontend/src/crm/api.ts index 6729db0..0627dca 100644 --- a/packages/frontend/src/crm/api.ts +++ b/packages/frontend/src/crm/api.ts @@ -16,6 +16,7 @@ import type { CreatePipelinePayload, UpdatePipelinePayload, CreateStagePayload, + UpdateStagePayload, PipelineStage, Activity, CreateActivityPayload, @@ -121,6 +122,14 @@ export const pipelinesApi = { ) .then((r) => r.data), + updateStage: (pipelineId: string, stageId: string, data: UpdateStagePayload) => + api + .patch>( + `/crm/pipelines/${pipelineId}/stages/${stageId}`, + data, + ) + .then((r) => r.data), + removeStage: (pipelineId: string, stageId: string) => api .delete>( diff --git a/packages/frontend/src/crm/contacts/ContactDetailPage.tsx b/packages/frontend/src/crm/contacts/ContactDetailPage.tsx index ba48081..d896c5d 100644 --- a/packages/frontend/src/crm/contacts/ContactDetailPage.tsx +++ b/packages/frontend/src/crm/contacts/ContactDetailPage.tsx @@ -309,11 +309,11 @@ export function ContactDetailPage() { )} - {/* Verknüpfte Deals */} + {/* Verknüpfte Vorgänge */} {deals.length > 0 && (

- Verknüpfte Deals ({deals.length}) + Verknüpfte Vorgänge ({deals.length})

diff --git a/packages/frontend/src/crm/deals/DealDetailPage.tsx b/packages/frontend/src/crm/deals/DealDetailPage.tsx index e7aee65..b20f3f6 100644 --- a/packages/frontend/src/crm/deals/DealDetailPage.tsx +++ b/packages/frontend/src/crm/deals/DealDetailPage.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { useParams, Link, useNavigate } from 'react-router-dom'; -import { useDeal, usePipeline, useDeleteDeal } from '../hooks'; +import { useDeal, useDeleteDeal } from '../hooks'; import { DealFormModal } from './DealFormModal'; import { Modal } from '../../components/Modal'; import type { DealStatus } from '../types'; @@ -39,10 +39,9 @@ export function DealDetailPage() { const deal = data?.data; - // Pipeline mit allen Stages laden fuer Fortschrittsbalken - const { data: pipelineData } = usePipeline(deal?.pipelineId ?? ''); - const pipelineStages = pipelineData?.data?.stages - ? [...pipelineData.data.stages].sort((a, b) => a.sortOrder - b.sortOrder) + // Pipeline-Stages direkt aus dem Deal-Objekt (Backend liefert alle Stages mit) + const pipelineStages = deal?.pipeline?.stages + ? [...deal.pipeline.stages].sort((a, b) => a.sortOrder - b.sortOrder) : []; const [isEditOpen, setEditOpen] = useState(false); @@ -52,7 +51,7 @@ export function DealDetailPage() { if (error || !deal) return (

- Deal konnte nicht geladen werden + Vorgang konnte nicht geladen werden

); @@ -78,7 +77,7 @@ export function DealDetailPage() { > - Zurück zu Deals + Zurück zu Vorgänge {/* Header */} @@ -155,7 +154,7 @@ export function DealDetailPage() { {/* Info Card */}
-

Deal-Details

+

Vorgangs-Details

Wert @@ -245,7 +244,7 @@ export function DealDetailPage() { setDeleteOpen(false)} - title="Deal löschen" + title="Vorgang löschen" maxWidth="420px" >

- Soll der Deal {deal.title} wirklich gelöscht werden? + Soll der Vorgang {deal.title} wirklich gelöscht werden?

@@ -260,7 +260,7 @@ export function DealFormModal({ style={inputStyle} value={title} onChange={(e) => setTitle(e.target.value)} - placeholder="Deal-Titel" + placeholder="Vorgangs-Titel" required />
diff --git a/packages/frontend/src/crm/deals/DealsPage.tsx b/packages/frontend/src/crm/deals/DealsPage.tsx index 02d5d36..e295a47 100644 --- a/packages/frontend/src/crm/deals/DealsPage.tsx +++ b/packages/frontend/src/crm/deals/DealsPage.tsx @@ -90,7 +90,7 @@ export function DealsPage() { if (error) return (

- Fehler beim Laden der Deals + Fehler beim Laden der Vorgänge

); @@ -108,7 +108,7 @@ export function DealsPage() { marginBottom: '1.5rem', }} > -

Deals

+

Vorgänge

- {pagination?.total ?? 0} Deals gesamt + {pagination?.total ?? 0} Vorgänge gesamt
@@ -229,7 +229,7 @@ export function DealsPage() { color: 'var(--color-text-muted)', }} > - Keine Deals gefunden + Keine Vorgänge gefunden )} @@ -389,14 +389,14 @@ export function DealsPage() { )}
- {/* Modal: Neuen Deal anlegen */} + {/* Modal: Neuen Vorgang anlegen */} setCreateOpen(false)} onSuccess={() => setCreateOpen(false)} /> - {/* Modal: Deal bearbeiten */} + {/* Modal: Vorgang bearbeiten */} setEditingDeal(null)} @@ -404,11 +404,11 @@ export function DealsPage() { onSuccess={() => setEditingDeal(null)} /> - {/* Modal: Deal löschen */} + {/* Modal: Vorgang löschen */} setDeletingDeal(null)} - title="Deal löschen" + title="Vorgang löschen" maxWidth="420px" >

- Soll der Deal {deletingDeal?.title} wirklich gelöscht + Soll der Vorgang {deletingDeal?.title} wirklich gelöscht werden?

pipelinesApi.updateStage(pipelineId, stageId, data), + onSuccess: () => { + qc.invalidateQueries({ queryKey: crmKeys.pipelines.all }); + }, + }); +} + export function useAddStage() { const qc = useQueryClient(); return useMutation({ diff --git a/packages/frontend/src/crm/pipelines/PipelinesPage.module.css b/packages/frontend/src/crm/pipelines/PipelinesPage.module.css index c8502d5..1c38862 100644 --- a/packages/frontend/src/crm/pipelines/PipelinesPage.module.css +++ b/packages/frontend/src/crm/pipelines/PipelinesPage.module.css @@ -108,6 +108,53 @@ background: none; } +.stageEditInput { + flex: 1; + padding: 0.25rem 0.5rem; + border: 1px solid var(--color-primary); + border-radius: var(--radius-sm); + font-size: 0.875rem; + outline: none; + background: var(--color-bg); + color: var(--color-text); +} + +.stageEditColor { + width: 28px; + height: 28px; + padding: 0; + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + cursor: pointer; + background: none; + flex-shrink: 0; +} + +.stageEditActions { + display: flex; + gap: 0.25rem; + flex-shrink: 0; +} + +.stageEditBtn { + padding: 0.125rem 0.375rem; + font-size: 0.75rem; + border-radius: var(--radius-sm); + border: none; + cursor: pointer; +} + +.stageEditSave { + background: var(--color-primary); + color: white; +} + +.stageEditCancel { + background: transparent; + border: 1px solid var(--color-border); + color: var(--color-text-muted); +} + .newPipelineForm { background: var(--color-bg-card); border: 2px dashed var(--color-border); diff --git a/packages/frontend/src/crm/pipelines/PipelinesPage.tsx b/packages/frontend/src/crm/pipelines/PipelinesPage.tsx index 80037ff..9750a93 100644 --- a/packages/frontend/src/crm/pipelines/PipelinesPage.tsx +++ b/packages/frontend/src/crm/pipelines/PipelinesPage.tsx @@ -4,12 +4,163 @@ import { useCreatePipeline, useDeletePipeline, useAddStage, + useUpdateStage, useRemoveStage, } from '../hooks'; import { Modal } from '../../components/Modal'; -import type { Pipeline } from '../types'; +import type { Pipeline, PipelineStage } from '../types'; import styles from './PipelinesPage.module.css'; +/* ------------------------------------------------------------------ */ +/* Einzelne Stage-Zeile (View + Edit) */ +/* ------------------------------------------------------------------ */ + +function StageRow({ + stage, + pipelineId, +}: { + stage: PipelineStage; + pipelineId: string; +}) { + const [isEditing, setIsEditing] = useState(false); + const [editName, setEditName] = useState(stage.name); + const [editColor, setEditColor] = useState(stage.color); + + const updateStageMutation = useUpdateStage(); + const removeStageMutation = useRemoveStage(); + + const handleSave = () => { + const changes: Record = {}; + if (editName.trim() !== stage.name) changes.name = editName.trim(); + if (editColor !== stage.color) changes.color = editColor; + + if (Object.keys(changes).length === 0) { + setIsEditing(false); + return; + } + + updateStageMutation.mutate( + { pipelineId, stageId: stage.id, data: changes }, + { onSuccess: () => setIsEditing(false) }, + ); + }; + + const handleCancel = () => { + setEditName(stage.name); + setEditColor(stage.color); + setIsEditing(false); + }; + + if (isEditing) { + return ( +
+ {stage.sortOrder + 1} + setEditColor(e.target.value)} + title="Farbe" + /> + setEditName(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleSave(); + } + if (e.key === 'Escape') handleCancel(); + }} + autoFocus + /> +
+ + +
+
+ ); + } + + return ( +
+ {stage.sortOrder + 1} + + setIsEditing(true)} + title="Doppelklick zum Bearbeiten" + style={{ cursor: 'pointer' }} + > + {stage.name} + + + +
+ ); +} + +/* ------------------------------------------------------------------ */ +/* Pipeline Card (klappbar) */ +/* ------------------------------------------------------------------ */ + function PipelineCard({ pipeline }: { pipeline: Pipeline }) { const [isOpen, setIsOpen] = useState(true); const [newStageName, setNewStageName] = useState(''); @@ -17,7 +168,6 @@ function PipelineCard({ pipeline }: { pipeline: Pipeline }) { const [isDeleteOpen, setDeleteOpen] = useState(false); const addStageMutation = useAddStage(); - const removeStageMutation = useRemoveStage(); const deletePipelineMutation = useDeletePipeline(); const stages = [...pipeline.stages].sort((a, b) => a.sortOrder - b.sortOrder); @@ -123,34 +273,11 @@ function PipelineCard({ pipeline }: { pipeline: Pipeline }) {

)} {stages.map((stage) => ( -
- {stage.sortOrder + 1} - - {stage.name} - -
+ ))} {/* Neue Stufe hinzufügen */} @@ -227,7 +354,7 @@ function PipelineCard({ pipeline }: { pipeline: Pipeline }) { marginBottom: '1.5rem', }} > - Alle Stufen und zugehörige Deals werden ebenfalls gelöscht. + Alle Stufen und zugehörige Vorgänge werden ebenfalls gelöscht.

- Erstelle eine Pipeline, um Deals zu verwalten. + Erstelle eine Pipeline, um Vorgänge zu verwalten.

)} diff --git a/packages/frontend/src/crm/types.ts b/packages/frontend/src/crm/types.ts index 867acd3..b3326ac 100644 --- a/packages/frontend/src/crm/types.ts +++ b/packages/frontend/src/crm/types.ts @@ -128,6 +128,12 @@ export interface UpdatePipelinePayload { isActive?: boolean; } +export interface UpdateStagePayload { + name?: string; + sortOrder?: number; + color?: string; +} + export interface CreateStagePayload { name: string; sortOrder?: number; @@ -153,7 +159,7 @@ export interface Deal { updatedBy: string | null; createdAt: string; updatedAt: string; - pipeline?: { id: string; name: string }; + pipeline?: { id: string; name: string; stages?: PipelineStage[] }; stage?: { id: string; name: string; color: string }; contact?: { id: string; diff --git a/packages/frontend/src/shell/AppLayout.tsx b/packages/frontend/src/shell/AppLayout.tsx index 6ebd349..a75a49c 100644 --- a/packages/frontend/src/shell/AppLayout.tsx +++ b/packages/frontend/src/shell/AppLayout.tsx @@ -285,7 +285,7 @@ export function AppLayout() { className={({ isActive }) => `${styles.navLink} ${isActive ? styles.active : ''}` } - title="Deals" + title="Vorgänge" > - {!collapsed && 'Deals'} + {!collapsed && 'Vorgänge'}