UI: Bestätigungsdialog vor allen Löschungen (Projekt + Lead)
Bisher loeschten 8 Muelltonnen-Icons sofort und unwiderruflich, ganz ohne Rueckfrage. Jetzt fragt jeder Loeschpfad ueber useConfirm() nach. - projekt.tsx: Aenderung, Stakeholder, Risiko, Meilenstein - lead.tsx: Kontakt, Touchpoint, offene Frage, Anhang Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
33a740ca7e
commit
90caa282ee
2 files changed
+90
-8
No files matched your search
@@ -10,6 +10,7 @@ import { voice } from '../lib/voice';
|
||||
import { Card } from '../components/ui/Card';
|
||||
import { Button } from '../components/ui/Button';
|
||||
import { SkeletonCard } from '../components/ui/SkeletonCard';
|
||||
import { useConfirm } from '../components/ui/ConfirmProvider';
|
||||
import {
|
||||
LEAD_STATUS_META,
|
||||
LEAD_STATUS_ORDER,
|
||||
@@ -591,6 +592,7 @@ function ContactRow({ leadId, contact }: { leadId: string; contact: Contact }) {
|
||||
await utils.lead.byId.invalidate({ id: leadId });
|
||||
},
|
||||
});
|
||||
const confirm = useConfirm();
|
||||
const details = [
|
||||
contact.email,
|
||||
contact.telefon,
|
||||
@@ -628,7 +630,16 @@ function ContactRow({ leadId, contact }: { leadId: string; contact: Contact }) {
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => remove.mutate({ contactId: contact.id })}
|
||||
onClick={async () => {
|
||||
if (
|
||||
await confirm({
|
||||
title: 'Kontakt entfernen?',
|
||||
destructive: true,
|
||||
confirmLabel: 'Entfernen',
|
||||
})
|
||||
)
|
||||
remove.mutate({ contactId: contact.id });
|
||||
}}
|
||||
aria-label="Kontakt entfernen"
|
||||
className="text-[var(--color-ink-faint)] hover:text-[var(--color-ampel-red-fg)]"
|
||||
>
|
||||
@@ -804,6 +815,7 @@ function TouchpointRow({ leadId, touchpoint }: { leadId: string; touchpoint: Tou
|
||||
await utils.lead.byId.invalidate({ id: leadId });
|
||||
},
|
||||
});
|
||||
const confirm = useConfirm();
|
||||
return (
|
||||
<div className="rounded-[var(--radius-md)] border border-[var(--color-border)] p-3 text-sm">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
@@ -821,7 +833,16 @@ function TouchpointRow({ leadId, touchpoint }: { leadId: string; touchpoint: Tou
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => remove.mutate({ touchpointId: touchpoint.id })}
|
||||
onClick={async () => {
|
||||
if (
|
||||
await confirm({
|
||||
title: 'Touchpoint löschen?',
|
||||
destructive: true,
|
||||
confirmLabel: 'Löschen',
|
||||
})
|
||||
)
|
||||
remove.mutate({ touchpointId: touchpoint.id });
|
||||
}}
|
||||
aria-label="Eintrag entfernen"
|
||||
className="text-[var(--color-ink-faint)] hover:text-[var(--color-ampel-red-fg)]"
|
||||
>
|
||||
@@ -934,6 +955,7 @@ function OffeneFragenSection({
|
||||
await utils.lead.byId.invalidate({ id: leadId });
|
||||
},
|
||||
});
|
||||
const confirm = useConfirm();
|
||||
function persist(next: Array<{ text: string; done: boolean }>) {
|
||||
update.mutate({ id: leadId, offeneFragen: next });
|
||||
}
|
||||
@@ -963,7 +985,16 @@ function OffeneFragenSection({
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => persist(fragen.filter((_, j) => j !== i))}
|
||||
onClick={async () => {
|
||||
if (
|
||||
await confirm({
|
||||
title: 'Frage löschen?',
|
||||
destructive: true,
|
||||
confirmLabel: 'Löschen',
|
||||
})
|
||||
)
|
||||
persist(fragen.filter((_, j) => j !== i));
|
||||
}}
|
||||
aria-label="Frage entfernen"
|
||||
className="text-[var(--color-ink-faint)] hover:text-[var(--color-ampel-red-fg)]"
|
||||
>
|
||||
@@ -1013,6 +1044,7 @@ function AnhaengeSection({
|
||||
await utils.lead.byId.invalidate({ id: leadId });
|
||||
},
|
||||
});
|
||||
const confirm = useConfirm();
|
||||
function persist(next: Array<{ label: string; url: string }>) {
|
||||
update.mutate({ id: leadId, anhaenge: next });
|
||||
}
|
||||
@@ -1031,7 +1063,16 @@ function AnhaengeSection({
|
||||
<ExternalLink url={a.url} label={a.label} />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => persist(anhaenge.filter((_, j) => j !== i))}
|
||||
onClick={async () => {
|
||||
if (
|
||||
await confirm({
|
||||
title: 'Anhang entfernen?',
|
||||
destructive: true,
|
||||
confirmLabel: 'Entfernen',
|
||||
})
|
||||
)
|
||||
persist(anhaenge.filter((_, j) => j !== i));
|
||||
}}
|
||||
aria-label="Anhang entfernen"
|
||||
className="text-[var(--color-ink-faint)] hover:text-[var(--color-ampel-red-fg)]"
|
||||
>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { trpc } from '../lib/trpc';
|
||||
import { Card } from '../components/ui/Card';
|
||||
import { Button } from '../components/ui/Button';
|
||||
import { SkeletonCard } from '../components/ui/SkeletonCard';
|
||||
import { useConfirm } from '../components/ui/ConfirmProvider';
|
||||
import { useCurrentUser } from '../hooks/useCurrentUser';
|
||||
import { voice } from '../lib/voice';
|
||||
import { toast } from 'sonner';
|
||||
@@ -1049,6 +1050,7 @@ function ChangeRow({ projectId, change: c }: { projectId: string; change: Change
|
||||
const invalidate = () => utils.change.forProject.invalidate({ projectId });
|
||||
const update = trpc.change.update.useMutation({ onSuccess: invalidate });
|
||||
const remove = trpc.change.remove.useMutation({ onSuccess: invalidate });
|
||||
const confirm = useConfirm();
|
||||
const meta = CHANGE_ENTSCHEIDUNG_META[c.entscheidung as ChangeEntscheidung];
|
||||
return (
|
||||
<div className="rounded-[var(--radius-md)] border border-[var(--color-border)] p-3 text-sm">
|
||||
@@ -1087,7 +1089,16 @@ function ChangeRow({ projectId, change: c }: { projectId: string; change: Change
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => remove.mutate({ id: c.id })}
|
||||
onClick={async () => {
|
||||
if (
|
||||
await confirm({
|
||||
title: 'Änderung löschen?',
|
||||
destructive: true,
|
||||
confirmLabel: 'Löschen',
|
||||
})
|
||||
)
|
||||
remove.mutate({ id: c.id });
|
||||
}}
|
||||
aria-label="Entfernen"
|
||||
className="text-[var(--color-ink-faint)] hover:text-[var(--color-ampel-red-fg)]"
|
||||
>
|
||||
@@ -1223,6 +1234,7 @@ function StakeholderRow({
|
||||
},
|
||||
});
|
||||
const remove = trpc.stakeholder.remove.useMutation({ onSuccess: invalidate });
|
||||
const confirm = useConfirm();
|
||||
|
||||
const [name, setName] = useState(s.name);
|
||||
const [rolle, setRolle] = useState(s.rolle ?? '');
|
||||
@@ -1341,7 +1353,16 @@ function StakeholderRow({
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => remove.mutate({ id: s.id })}
|
||||
onClick={async () => {
|
||||
if (
|
||||
await confirm({
|
||||
title: 'Stakeholder entfernen?',
|
||||
destructive: true,
|
||||
confirmLabel: 'Entfernen',
|
||||
})
|
||||
)
|
||||
remove.mutate({ id: s.id });
|
||||
}}
|
||||
aria-label="Entfernen"
|
||||
className="text-[var(--color-ink-faint)] hover:text-[var(--color-ampel-red-fg)]"
|
||||
>
|
||||
@@ -1673,6 +1694,7 @@ function RiskRow({
|
||||
},
|
||||
});
|
||||
const remove = trpc.risk.remove.useMutation({ onSuccess: invalidate });
|
||||
const confirm = useConfirm();
|
||||
|
||||
const [title, setTitle] = useState(r.title);
|
||||
const [wahr, setWahr] = useState<Level>(r.wahrscheinlichkeit as Level);
|
||||
@@ -1828,7 +1850,16 @@ function RiskRow({
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => remove.mutate({ id: r.id })}
|
||||
onClick={async () => {
|
||||
if (
|
||||
await confirm({
|
||||
title: 'Risiko löschen?',
|
||||
destructive: true,
|
||||
confirmLabel: 'Löschen',
|
||||
})
|
||||
)
|
||||
remove.mutate({ id: r.id });
|
||||
}}
|
||||
aria-label="Entfernen"
|
||||
className="text-[var(--color-ink-faint)] hover:text-[var(--color-ampel-red-fg)]"
|
||||
>
|
||||
@@ -2061,6 +2092,7 @@ function MilestoneRow({ projectId, milestone: m }: { projectId: string; mileston
|
||||
},
|
||||
});
|
||||
const remove = trpc.project.removeMilestone.useMutation({ onSuccess: invalidate });
|
||||
const confirm = useConfirm();
|
||||
|
||||
const [title, setTitle] = useState(m.title);
|
||||
const [due, setDue] = useState(m.due ?? '');
|
||||
@@ -2174,7 +2206,16 @@ function MilestoneRow({ projectId, milestone: m }: { projectId: string; mileston
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => remove.mutate({ milestoneId: m.id })}
|
||||
onClick={async () => {
|
||||
if (
|
||||
await confirm({
|
||||
title: 'Meilenstein löschen?',
|
||||
destructive: true,
|
||||
confirmLabel: 'Löschen',
|
||||
})
|
||||
)
|
||||
remove.mutate({ milestoneId: m.id });
|
||||
}}
|
||||
aria-label="Entfernen"
|
||||
className="text-[var(--color-ink-faint)] hover:text-[var(--color-ampel-red-fg)]"
|
||||
>
|
||||
|
||||
Reference in new issue
Block a user