mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-25 01:36:39 +02:00
feat(crm): inline stage editing, DealDetail optimization, rename Deals to Vorgänge
- PipelinesPage: Stages können jetzt per Doppelklick oder Stift-Icon inline bearbeitet werden (Name, Farbe) via PATCH endpoint - DealDetailPage: Nutzt jetzt deal.pipeline.stages direkt statt separatem usePipeline() API-Call (Backend liefert alle Stages mit) - UI-Texte: "Deals" → "Vorgänge", "Deal" → "Vorgang" in allen user-facing Strings (Sidebar, Seiten, Modals, Fehlermeldungen) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
56a9ed9647
commit
0b78160f33
10 changed files with 270 additions and 59 deletions
|
|
@ -16,6 +16,7 @@ import type {
|
||||||
CreatePipelinePayload,
|
CreatePipelinePayload,
|
||||||
UpdatePipelinePayload,
|
UpdatePipelinePayload,
|
||||||
CreateStagePayload,
|
CreateStagePayload,
|
||||||
|
UpdateStagePayload,
|
||||||
PipelineStage,
|
PipelineStage,
|
||||||
Activity,
|
Activity,
|
||||||
CreateActivityPayload,
|
CreateActivityPayload,
|
||||||
|
|
@ -121,6 +122,14 @@ export const pipelinesApi = {
|
||||||
)
|
)
|
||||||
.then((r) => r.data),
|
.then((r) => r.data),
|
||||||
|
|
||||||
|
updateStage: (pipelineId: string, stageId: string, data: UpdateStagePayload) =>
|
||||||
|
api
|
||||||
|
.patch<SingleResponse<PipelineStage>>(
|
||||||
|
`/crm/pipelines/${pipelineId}/stages/${stageId}`,
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
.then((r) => r.data),
|
||||||
|
|
||||||
removeStage: (pipelineId: string, stageId: string) =>
|
removeStage: (pipelineId: string, stageId: string) =>
|
||||||
api
|
api
|
||||||
.delete<SingleResponse<PipelineStage>>(
|
.delete<SingleResponse<PipelineStage>>(
|
||||||
|
|
|
||||||
|
|
@ -309,11 +309,11 @@ export function ContactDetailPage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Verknüpfte Deals */}
|
{/* Verknüpfte Vorgänge */}
|
||||||
{deals.length > 0 && (
|
{deals.length > 0 && (
|
||||||
<div className={styles.card} style={{ marginTop: '1.5rem' }}>
|
<div className={styles.card} style={{ marginTop: '1.5rem' }}>
|
||||||
<h2 className={styles.cardTitle}>
|
<h2 className={styles.cardTitle}>
|
||||||
Verknüpfte Deals ({deals.length})
|
Verknüpfte Vorgänge ({deals.length})
|
||||||
</h2>
|
</h2>
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||||
<thead>
|
<thead>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useParams, Link, useNavigate } from 'react-router-dom';
|
import { useParams, Link, useNavigate } from 'react-router-dom';
|
||||||
import { useDeal, usePipeline, useDeleteDeal } from '../hooks';
|
import { useDeal, useDeleteDeal } from '../hooks';
|
||||||
import { DealFormModal } from './DealFormModal';
|
import { DealFormModal } from './DealFormModal';
|
||||||
import { Modal } from '../../components/Modal';
|
import { Modal } from '../../components/Modal';
|
||||||
import type { DealStatus } from '../types';
|
import type { DealStatus } from '../types';
|
||||||
|
|
@ -39,10 +39,9 @@ export function DealDetailPage() {
|
||||||
|
|
||||||
const deal = data?.data;
|
const deal = data?.data;
|
||||||
|
|
||||||
// Pipeline mit allen Stages laden fuer Fortschrittsbalken
|
// Pipeline-Stages direkt aus dem Deal-Objekt (Backend liefert alle Stages mit)
|
||||||
const { data: pipelineData } = usePipeline(deal?.pipelineId ?? '');
|
const pipelineStages = deal?.pipeline?.stages
|
||||||
const pipelineStages = pipelineData?.data?.stages
|
? [...deal.pipeline.stages].sort((a, b) => a.sortOrder - b.sortOrder)
|
||||||
? [...pipelineData.data.stages].sort((a, b) => a.sortOrder - b.sortOrder)
|
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const [isEditOpen, setEditOpen] = useState(false);
|
const [isEditOpen, setEditOpen] = useState(false);
|
||||||
|
|
@ -52,7 +51,7 @@ export function DealDetailPage() {
|
||||||
if (error || !deal)
|
if (error || !deal)
|
||||||
return (
|
return (
|
||||||
<p style={{ color: 'var(--color-error)' }}>
|
<p style={{ color: 'var(--color-error)' }}>
|
||||||
Deal konnte nicht geladen werden
|
Vorgang konnte nicht geladen werden
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -78,7 +77,7 @@ export function DealDetailPage() {
|
||||||
>
|
>
|
||||||
<path d="M9 2L4 7l5 5" />
|
<path d="M9 2L4 7l5 5" />
|
||||||
</svg>
|
</svg>
|
||||||
Zurück zu Deals
|
Zurück zu Vorgänge
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
|
|
@ -155,7 +154,7 @@ export function DealDetailPage() {
|
||||||
|
|
||||||
{/* Info Card */}
|
{/* Info Card */}
|
||||||
<div className={styles.card}>
|
<div className={styles.card}>
|
||||||
<h2 className={styles.cardTitle}>Deal-Details</h2>
|
<h2 className={styles.cardTitle}>Vorgangs-Details</h2>
|
||||||
<div className={styles.infoGrid}>
|
<div className={styles.infoGrid}>
|
||||||
<span className={styles.infoLabel}>Wert</span>
|
<span className={styles.infoLabel}>Wert</span>
|
||||||
<span className={styles.infoValue} style={{ fontWeight: 600 }}>
|
<span className={styles.infoValue} style={{ fontWeight: 600 }}>
|
||||||
|
|
@ -245,7 +244,7 @@ export function DealDetailPage() {
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isDeleteOpen}
|
isOpen={isDeleteOpen}
|
||||||
onClose={() => setDeleteOpen(false)}
|
onClose={() => setDeleteOpen(false)}
|
||||||
title="Deal löschen"
|
title="Vorgang löschen"
|
||||||
maxWidth="420px"
|
maxWidth="420px"
|
||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
|
|
@ -255,7 +254,7 @@ export function DealDetailPage() {
|
||||||
marginBottom: '1.5rem',
|
marginBottom: '1.5rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Soll der Deal <strong>{deal.title}</strong> wirklich gelöscht werden?
|
Soll der Vorgang <strong>{deal.title}</strong> wirklich gelöscht werden?
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ export function DealFormModal({
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={isEditMode ? 'Deal bearbeiten' : 'Neuer Deal'}
|
title={isEditMode ? 'Vorgang bearbeiten' : 'Neuer Vorgang'}
|
||||||
maxWidth="600px"
|
maxWidth="600px"
|
||||||
>
|
>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
|
|
@ -260,7 +260,7 @@ export function DealFormModal({
|
||||||
style={inputStyle}
|
style={inputStyle}
|
||||||
value={title}
|
value={title}
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
placeholder="Deal-Titel"
|
placeholder="Vorgangs-Titel"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ export function DealsPage() {
|
||||||
if (error)
|
if (error)
|
||||||
return (
|
return (
|
||||||
<p style={{ color: 'var(--color-error)' }}>
|
<p style={{ color: 'var(--color-error)' }}>
|
||||||
Fehler beim Laden der Deals
|
Fehler beim Laden der Vorgänge
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -108,7 +108,7 @@ export function DealsPage() {
|
||||||
marginBottom: '1.5rem',
|
marginBottom: '1.5rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h1 style={{ fontSize: '1.5rem', fontWeight: 600 }}>Deals</h1>
|
<h1 style={{ fontSize: '1.5rem', fontWeight: 600 }}>Vorgänge</h1>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -116,7 +116,7 @@ export function DealsPage() {
|
||||||
fontSize: '0.875rem',
|
fontSize: '0.875rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{pagination?.total ?? 0} Deals gesamt
|
{pagination?.total ?? 0} Vorgänge gesamt
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => setCreateOpen(true)}
|
onClick={() => setCreateOpen(true)}
|
||||||
|
|
@ -131,7 +131,7 @@ export function DealsPage() {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
+ Neuer Deal
|
+ Neuer Vorgang
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -229,7 +229,7 @@ export function DealsPage() {
|
||||||
color: 'var(--color-text-muted)',
|
color: 'var(--color-text-muted)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Keine Deals gefunden
|
Keine Vorgänge gefunden
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
|
|
@ -389,14 +389,14 @@ export function DealsPage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Modal: Neuen Deal anlegen */}
|
{/* Modal: Neuen Vorgang anlegen */}
|
||||||
<DealFormModal
|
<DealFormModal
|
||||||
isOpen={isCreateOpen}
|
isOpen={isCreateOpen}
|
||||||
onClose={() => setCreateOpen(false)}
|
onClose={() => setCreateOpen(false)}
|
||||||
onSuccess={() => setCreateOpen(false)}
|
onSuccess={() => setCreateOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Modal: Deal bearbeiten */}
|
{/* Modal: Vorgang bearbeiten */}
|
||||||
<DealFormModal
|
<DealFormModal
|
||||||
isOpen={!!editingDeal}
|
isOpen={!!editingDeal}
|
||||||
onClose={() => setEditingDeal(null)}
|
onClose={() => setEditingDeal(null)}
|
||||||
|
|
@ -404,11 +404,11 @@ export function DealsPage() {
|
||||||
onSuccess={() => setEditingDeal(null)}
|
onSuccess={() => setEditingDeal(null)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Modal: Deal löschen */}
|
{/* Modal: Vorgang löschen */}
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={!!deletingDeal}
|
isOpen={!!deletingDeal}
|
||||||
onClose={() => setDeletingDeal(null)}
|
onClose={() => setDeletingDeal(null)}
|
||||||
title="Deal löschen"
|
title="Vorgang löschen"
|
||||||
maxWidth="420px"
|
maxWidth="420px"
|
||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
|
|
@ -418,7 +418,7 @@ export function DealsPage() {
|
||||||
marginBottom: '1.5rem',
|
marginBottom: '1.5rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Soll der Deal <strong>{deletingDeal?.title}</strong> wirklich gelöscht
|
Soll der Vorgang <strong>{deletingDeal?.title}</strong> wirklich gelöscht
|
||||||
werden?
|
werden?
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import type {
|
||||||
CreatePipelinePayload,
|
CreatePipelinePayload,
|
||||||
UpdatePipelinePayload,
|
UpdatePipelinePayload,
|
||||||
CreateStagePayload,
|
CreateStagePayload,
|
||||||
|
UpdateStagePayload,
|
||||||
CreateActivityPayload,
|
CreateActivityPayload,
|
||||||
UpdateActivityPayload,
|
UpdateActivityPayload,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
@ -203,6 +204,24 @@ export function useDeletePipeline() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useUpdateStage() {
|
||||||
|
const qc = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: ({
|
||||||
|
pipelineId,
|
||||||
|
stageId,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
pipelineId: string;
|
||||||
|
stageId: string;
|
||||||
|
data: UpdateStagePayload;
|
||||||
|
}) => pipelinesApi.updateStage(pipelineId, stageId, data),
|
||||||
|
onSuccess: () => {
|
||||||
|
qc.invalidateQueries({ queryKey: crmKeys.pipelines.all });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function useAddStage() {
|
export function useAddStage() {
|
||||||
const qc = useQueryClient();
|
const qc = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,53 @@
|
||||||
background: none;
|
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 {
|
.newPipelineForm {
|
||||||
background: var(--color-bg-card);
|
background: var(--color-bg-card);
|
||||||
border: 2px dashed var(--color-border);
|
border: 2px dashed var(--color-border);
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,163 @@ import {
|
||||||
useCreatePipeline,
|
useCreatePipeline,
|
||||||
useDeletePipeline,
|
useDeletePipeline,
|
||||||
useAddStage,
|
useAddStage,
|
||||||
|
useUpdateStage,
|
||||||
useRemoveStage,
|
useRemoveStage,
|
||||||
} from '../hooks';
|
} from '../hooks';
|
||||||
import { Modal } from '../../components/Modal';
|
import { Modal } from '../../components/Modal';
|
||||||
import type { Pipeline } from '../types';
|
import type { Pipeline, PipelineStage } from '../types';
|
||||||
import styles from './PipelinesPage.module.css';
|
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<string, string> = {};
|
||||||
|
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 (
|
||||||
|
<div className={styles.stageItem}>
|
||||||
|
<span className={styles.stageOrder}>{stage.sortOrder + 1}</span>
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
className={styles.stageEditColor}
|
||||||
|
value={editColor}
|
||||||
|
onChange={(e) => setEditColor(e.target.value)}
|
||||||
|
title="Farbe"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className={styles.stageEditInput}
|
||||||
|
value={editName}
|
||||||
|
onChange={(e) => setEditName(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSave();
|
||||||
|
}
|
||||||
|
if (e.key === 'Escape') handleCancel();
|
||||||
|
}}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<div className={styles.stageEditActions}>
|
||||||
|
<button
|
||||||
|
className={`${styles.stageEditBtn} ${styles.stageEditSave}`}
|
||||||
|
onClick={handleSave}
|
||||||
|
disabled={!editName.trim() || updateStageMutation.isPending}
|
||||||
|
>
|
||||||
|
{updateStageMutation.isPending ? '...' : '✓'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`${styles.stageEditBtn} ${styles.stageEditCancel}`}
|
||||||
|
onClick={handleCancel}
|
||||||
|
disabled={updateStageMutation.isPending}
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.stageItem}>
|
||||||
|
<span className={styles.stageOrder}>{stage.sortOrder + 1}</span>
|
||||||
|
<span
|
||||||
|
className={styles.stageColor}
|
||||||
|
style={{ background: stage.color }}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={styles.stageName}
|
||||||
|
onDoubleClick={() => setIsEditing(true)}
|
||||||
|
title="Doppelklick zum Bearbeiten"
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
>
|
||||||
|
{stage.name}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsEditing(true)}
|
||||||
|
style={{
|
||||||
|
background: 'none',
|
||||||
|
border: 'none',
|
||||||
|
color: 'var(--color-text-muted)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '0.8125rem',
|
||||||
|
padding: '0 0.25rem',
|
||||||
|
}}
|
||||||
|
title="Stufe bearbeiten"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
viewBox="0 0 14 14"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.3"
|
||||||
|
>
|
||||||
|
<path d="M8.5 2.5l3 3M1.5 9.5l6-6 3 3-6 6H1.5v-3z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
removeStageMutation.mutate({
|
||||||
|
pipelineId,
|
||||||
|
stageId: stage.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={removeStageMutation.isPending}
|
||||||
|
style={{
|
||||||
|
background: 'none',
|
||||||
|
border: 'none',
|
||||||
|
color: 'var(--color-text-muted)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '1rem',
|
||||||
|
padding: '0 0.25rem',
|
||||||
|
}}
|
||||||
|
title="Stufe entfernen"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Pipeline Card (klappbar) */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
function PipelineCard({ pipeline }: { pipeline: Pipeline }) {
|
function PipelineCard({ pipeline }: { pipeline: Pipeline }) {
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
const [isOpen, setIsOpen] = useState(true);
|
||||||
const [newStageName, setNewStageName] = useState('');
|
const [newStageName, setNewStageName] = useState('');
|
||||||
|
|
@ -17,7 +168,6 @@ function PipelineCard({ pipeline }: { pipeline: Pipeline }) {
|
||||||
const [isDeleteOpen, setDeleteOpen] = useState(false);
|
const [isDeleteOpen, setDeleteOpen] = useState(false);
|
||||||
|
|
||||||
const addStageMutation = useAddStage();
|
const addStageMutation = useAddStage();
|
||||||
const removeStageMutation = useRemoveStage();
|
|
||||||
const deletePipelineMutation = useDeletePipeline();
|
const deletePipelineMutation = useDeletePipeline();
|
||||||
|
|
||||||
const stages = [...pipeline.stages].sort((a, b) => a.sortOrder - b.sortOrder);
|
const stages = [...pipeline.stages].sort((a, b) => a.sortOrder - b.sortOrder);
|
||||||
|
|
@ -123,34 +273,11 @@ function PipelineCard({ pipeline }: { pipeline: Pipeline }) {
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{stages.map((stage) => (
|
{stages.map((stage) => (
|
||||||
<div key={stage.id} className={styles.stageItem}>
|
<StageRow
|
||||||
<span className={styles.stageOrder}>{stage.sortOrder + 1}</span>
|
key={stage.id}
|
||||||
<span
|
stage={stage}
|
||||||
className={styles.stageColor}
|
pipelineId={pipeline.id}
|
||||||
style={{ background: stage.color }}
|
/>
|
||||||
/>
|
|
||||||
<span className={styles.stageName}>{stage.name}</span>
|
|
||||||
<button
|
|
||||||
onClick={() =>
|
|
||||||
removeStageMutation.mutate({
|
|
||||||
pipelineId: pipeline.id,
|
|
||||||
stageId: stage.id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
disabled={removeStageMutation.isPending}
|
|
||||||
style={{
|
|
||||||
background: 'none',
|
|
||||||
border: 'none',
|
|
||||||
color: 'var(--color-text-muted)',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontSize: '1rem',
|
|
||||||
padding: '0 0.25rem',
|
|
||||||
}}
|
|
||||||
title="Stufe entfernen"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Neue Stufe hinzufügen */}
|
{/* Neue Stufe hinzufügen */}
|
||||||
|
|
@ -227,7 +354,7 @@ function PipelineCard({ pipeline }: { pipeline: Pipeline }) {
|
||||||
marginBottom: '1.5rem',
|
marginBottom: '1.5rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Alle Stufen und zugehörige Deals werden ebenfalls gelöscht.
|
Alle Stufen und zugehörige Vorgänge werden ebenfalls gelöscht.
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -280,6 +407,10 @@ function PipelineCard({ pipeline }: { pipeline: Pipeline }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Hauptseite */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
export function PipelinesPage() {
|
export function PipelinesPage() {
|
||||||
const { data, isLoading, error } = usePipelines();
|
const { data, isLoading, error } = usePipelines();
|
||||||
const createMutation = useCreatePipeline();
|
const createMutation = useCreatePipeline();
|
||||||
|
|
@ -471,7 +602,7 @@ export function PipelinesPage() {
|
||||||
Noch keine Pipelines vorhanden
|
Noch keine Pipelines vorhanden
|
||||||
</p>
|
</p>
|
||||||
<p style={{ fontSize: '0.875rem' }}>
|
<p style={{ fontSize: '0.875rem' }}>
|
||||||
Erstelle eine Pipeline, um Deals zu verwalten.
|
Erstelle eine Pipeline, um Vorgänge zu verwalten.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,12 @@ export interface UpdatePipelinePayload {
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateStagePayload {
|
||||||
|
name?: string;
|
||||||
|
sortOrder?: number;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CreateStagePayload {
|
export interface CreateStagePayload {
|
||||||
name: string;
|
name: string;
|
||||||
sortOrder?: number;
|
sortOrder?: number;
|
||||||
|
|
@ -153,7 +159,7 @@ export interface Deal {
|
||||||
updatedBy: string | null;
|
updatedBy: string | null;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
pipeline?: { id: string; name: string };
|
pipeline?: { id: string; name: string; stages?: PipelineStage[] };
|
||||||
stage?: { id: string; name: string; color: string };
|
stage?: { id: string; name: string; color: string };
|
||||||
contact?: {
|
contact?: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
||||||
|
|
@ -285,7 +285,7 @@ export function AppLayout() {
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`${styles.navLink} ${isActive ? styles.active : ''}`
|
`${styles.navLink} ${isActive ? styles.active : ''}`
|
||||||
}
|
}
|
||||||
title="Deals"
|
title="Vorgänge"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
width="16"
|
width="16"
|
||||||
|
|
@ -301,7 +301,7 @@ export function AppLayout() {
|
||||||
<path d="M5 5V3a2 2 0 012-2h2a2 2 0 012 2v2" />
|
<path d="M5 5V3a2 2 0 012-2h2a2 2 0 012 2v2" />
|
||||||
<path d="M1 9h14" />
|
<path d="M1 9h14" />
|
||||||
</svg>
|
</svg>
|
||||||
{!collapsed && 'Deals'}
|
{!collapsed && 'Vorgänge'}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/crm/pipelines"
|
to="/crm/pipelines"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue