feat(akquise): Mail an Berater bei neuem Projekt
Wird in der Akquise ein Projekt mit Berater angelegt (oder ein Berater beim Bearbeiten hinzugefuegt), bekommt er eine Mail ueber Graph sendMail aus crm@perinco.de; Reply-To ist der eintragende Benutzer. Je Berater abschaltbar unter Einstellungen -> Akquise (Spalte notify, Vorgabe an). Versand laeuft nach der Antwort und kippt das Speichern nie. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
907b010f3c
commit
eaef454442
@@ -84,13 +84,53 @@ const EmailField: React.FC<{
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
* Schalter "Mail bei neuem Projekt". Ohne Adresse bleibt er bedienbar, sagt
|
||||
* aber, dass so keine Mail herausgeht - sonst wirkt er an und tut nichts.
|
||||
*/
|
||||
const NotifyToggle: React.FC<{
|
||||
option: AcquisitionOption;
|
||||
onChange: (notify: boolean) => Promise<void>;
|
||||
}> = ({ option, onChange }) => {
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const toggle = async (notify: boolean) => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await onChange(notify);
|
||||
} catch {
|
||||
// Meldung kommt aus onChange, der Haken bleibt auf dem gespeicherten Stand.
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<label className="mt-1.5 flex cursor-pointer items-center gap-2 text-xs text-ink-600">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={option.notify}
|
||||
disabled={isSaving}
|
||||
onChange={(event) => toggle(event.target.checked)}
|
||||
className="h-4 w-4 accent-brand-500"
|
||||
/>
|
||||
Mail bei neuem Projekt
|
||||
{option.notify && !option.email && (
|
||||
<span className="text-amber-600">- ohne Adresse geht keine Mail heraus</span>
|
||||
)}
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
const OptionRow: React.FC<{
|
||||
option: AcquisitionOption;
|
||||
onRenamed: (value: string) => Promise<void>;
|
||||
onDelete: () => void;
|
||||
/** Nur bei "Berater" gesetzt - dann zeigt die Zeile das Adressfeld. */
|
||||
onEmailSaved?: (email: string) => Promise<void>;
|
||||
}> = ({ option, onRenamed, onDelete, onEmailSaved }) => {
|
||||
/** Nur bei "Berater" gesetzt - Schalter fuer die Mail bei neuem Projekt. */
|
||||
onNotifyChanged?: (notify: boolean) => Promise<void>;
|
||||
}> = ({ option, onRenamed, onDelete, onEmailSaved, onNotifyChanged }) => {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [draft, setDraft] = useState(option.value);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
@@ -125,6 +165,7 @@ const OptionRow: React.FC<{
|
||||
<EmailField option={option} onSave={onEmailSaved} />
|
||||
</div>
|
||||
)}
|
||||
{onNotifyChanged && <NotifyToggle option={option} onChange={onNotifyChanged} />}
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -262,6 +303,22 @@ export const AcquisitionSettings: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const saveNotify = async (option: AcquisitionOption, notify: boolean) => {
|
||||
try {
|
||||
await acquisitionOptionsApi.updateNotify(option.id, notify);
|
||||
addToast(
|
||||
notify
|
||||
? `${option.value} bekommt eine Mail bei neuen Projekten.`
|
||||
: `Keine Mails mehr an ${option.value}.`,
|
||||
'success'
|
||||
);
|
||||
await load(true);
|
||||
} catch (err) {
|
||||
addToast(err instanceof Error ? err.message : 'Speichern fehlgeschlagen.', 'error');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const confirmDelete = async () => {
|
||||
if (!toDelete) return;
|
||||
try {
|
||||
@@ -364,6 +421,9 @@ export const AcquisitionSettings: React.FC = () => {
|
||||
onEmailSaved={
|
||||
field === 'consultant' ? (email) => saveEmail(option, email) : undefined
|
||||
}
|
||||
onNotifyChanged={
|
||||
field === 'consultant' ? (notify) => saveNotify(option, notify) : undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -510,6 +510,13 @@ export const acquisitionOptionsApi = {
|
||||
{ method: 'PATCH', body: JSON.stringify({ email }) }
|
||||
),
|
||||
|
||||
/** Mail an den Berater bei neuem Projekt an- oder abschalten. */
|
||||
updateNotify: (id: number, notify: boolean) =>
|
||||
request<AcquisitionOption & { field: AcquisitionOptionField; updated: number }>(
|
||||
`/acquisitions/options/${id}`,
|
||||
{ method: 'PATCH', body: JSON.stringify({ notify }) }
|
||||
),
|
||||
|
||||
/** Entfernt nur die Auswahl; `still_in_use` zaehlt die Eintraege, die den Wert behalten. */
|
||||
remove: (id: number) =>
|
||||
request<{ ok: true; still_in_use: number }>(`/acquisitions/options/${id}`, { method: 'DELETE' }),
|
||||
|
||||
@@ -399,6 +399,8 @@ export interface AcquisitionOption {
|
||||
value: string;
|
||||
/** Nur bei "Berater" gepflegt, sonst immer null. */
|
||||
email: string | null;
|
||||
/** Nur bei "Berater": Mail, sobald er in der Akquise einem Projekt zugeordnet wird. */
|
||||
notify: boolean;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user