feat(opportunities): Uebernahme zurueck in die Akquise schieben
Im Dialog einer Opportunity aus der Akquise nimmt "Zurueck in die Akquise" eine versehentliche Uebernahme zurueck: alle Opportunities aus dem Eintrag wandern in den Papierkorb, der Eintrag steht mit seinem Funddatum wieder in Eingang bzw. Archiv. Neue Spalte crm_opportunities.acquisition_id, damit bei mehreren Beratern alle gefunden werden. Wiederherstellen haengt den Eintrag wieder an die Opportunity. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0ad7b32dbf
commit
33dedaedfb
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Modal } from '../ui/Modal';
|
||||
import { ConfirmDialog, Modal } from '../ui/Modal';
|
||||
import { LeadModal } from '../leads/LeadModal';
|
||||
import { DictateTextarea, DictateButton, DictationHint } from '../ui/DictationField';
|
||||
import { Avatar, inputClass, labelClass, labelRowClass, labelTextClass } from '../leads/leadUi';
|
||||
@@ -96,6 +96,8 @@ export const OpportunityModal: React.FC<OpportunityModalProps> = ({
|
||||
const [isCreatingLead, setIsCreatingLead] = useState(false);
|
||||
const [tagInput, setTagInput] = useState('');
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
// Rueckfrage vor "Zurueck in die Akquise" - der Schritt raeumt die Opportunity weg.
|
||||
const [confirmBack, setConfirmBack] = useState(false);
|
||||
|
||||
/*
|
||||
* Die Berater kommen aus derselben Auswahlliste wie in der Akquise. Geladen
|
||||
@@ -223,6 +225,32 @@ export const OpportunityModal: React.FC<OpportunityModalProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Uebernahme zuruecknehmen. Ungespeicherte Aenderungen im Formular gehen dabei
|
||||
* verloren - die Opportunity landet ohnehin im Papierkorb, und die Rueckfrage
|
||||
* sagt, was passiert.
|
||||
*/
|
||||
const moveBackToAcquisition = async () => {
|
||||
if (!opportunity) return;
|
||||
setConfirmBack(false);
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const result = await opportunitiesApi.backToAcquisition(opportunity.id);
|
||||
addToast(
|
||||
result.trashed.length > 1
|
||||
? `„${opportunity.title}" steht wieder in der Akquise, die ${result.trashed.length} Opportunities liegen im Papierkorb.`
|
||||
: `„${opportunity.title}" steht wieder in der Akquise, die Opportunity liegt im Papierkorb.`,
|
||||
'success'
|
||||
);
|
||||
onSaved();
|
||||
onClose();
|
||||
} catch (err) {
|
||||
addToast(err instanceof Error ? err.message : 'Zurücknehmen fehlgeschlagen.', 'error');
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
@@ -239,7 +267,22 @@ export const OpportunityModal: React.FC<OpportunityModalProps> = ({
|
||||
onClose={onClose}
|
||||
size="xl"
|
||||
footer={
|
||||
<div className="flex gap-3">
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{/* Nur bei Opportunities aus der Akquise, deren Eintrag noch da ist. */}
|
||||
{opportunity?.acquisition_available && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConfirmBack(true)}
|
||||
disabled={isSaving}
|
||||
title="Übernahme zurücknehmen: der Eintrag steht wieder in der Akquise"
|
||||
className="flex items-center justify-center gap-2 rounded-xl border border-ink-200 px-4 py-3 text-sm font-bold text-ink-600 transition hover:border-brand-300 hover:text-brand-700 disabled:opacity-60"
|
||||
>
|
||||
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" />
|
||||
</svg>
|
||||
Zurück in die Akquise
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
@@ -751,6 +794,16 @@ export const OpportunityModal: React.FC<OpportunityModalProps> = ({
|
||||
onSaved={onSaved}
|
||||
/>
|
||||
)}
|
||||
{confirmBack && opportunity && (
|
||||
<ConfirmDialog
|
||||
title="Zurück in die Akquise?"
|
||||
message={`„${opportunity.title}" steht danach wieder in der Akquise, mit seinem ursprünglichen Funddatum. Die Opportunity wandert in den Papierkorb, bei mehreren Beratern alle aus diesem Eintrag. Ungespeicherte Änderungen gehen verloren.`}
|
||||
confirmLabel="Zurückschieben"
|
||||
tone="neutral"
|
||||
onConfirm={moveBackToAcquisition}
|
||||
onCancel={() => setConfirmBack(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -223,6 +223,15 @@ export const opportunitiesApi = {
|
||||
remove: (id: number) => request<{ ok: true }>(`/opportunities/${id}`, { method: 'DELETE' }),
|
||||
restore: (id: number) =>
|
||||
request<Opportunity>(`/opportunities/${id}/restore`, { method: 'POST' }),
|
||||
/**
|
||||
* Nimmt die Uebernahme aus der Akquise zurueck: alle Opportunities aus dem
|
||||
* Eintrag wandern in den Papierkorb, der Eintrag steht wieder in der Akquise.
|
||||
*/
|
||||
backToAcquisition: (id: number) =>
|
||||
request<{ ok: true; acquisition_id: number; trashed: number[] }>(
|
||||
`/opportunities/${id}/acquisition`,
|
||||
{ method: 'POST' }
|
||||
),
|
||||
emptyTrash: () =>
|
||||
request<{ ok: true; purged: number }>('/opportunities/trash/empty', { method: 'POST' }),
|
||||
};
|
||||
|
||||
@@ -296,6 +296,13 @@ export interface Opportunity {
|
||||
* entstehen bei der Uebernahme drei Opportunities.
|
||||
*/
|
||||
consultant: string | null;
|
||||
/**
|
||||
* Eintrag der Akquise, aus dem die Opportunity uebernommen wurde - null bei
|
||||
* von Hand angelegten. Bei drei Beratern zeigen alle drei auf denselben.
|
||||
*/
|
||||
acquisition_id: number | null;
|
||||
/** Der Eintrag existiert noch und liegt nicht im Papierkorb: der Rueckweg ist offen. */
|
||||
acquisition_available?: boolean;
|
||||
description: string | null;
|
||||
stage: OpportunityStage;
|
||||
/** 0-100 */
|
||||
|
||||
Reference in New Issue
Block a user