feat(kontakte): Art einer Verbindung nachtraeglich aendern
Bisher liess sich eine angelegte Verbindung nur loeschen und neu anlegen, nur um etwa "Kollege" in "Vorgesetzter" zu drehen. Die Art steht jetzt als Auswahlfeld in jeder Verbindungszeile - im Kontaktdialog und in der Seitenleiste der Netzwerkansicht. Gespeichert wird sofort per PATCH /api/connections/:id, unabhaengig vom Speichern-Knopf des Formulars: eine Verbindung ist ein eigener Datensatz und haengt nicht am Kontaktformular, genau wie das Anlegen und Entfernen daneben. Backend und connectionsApi.update() gab es bereits, nur die Oberflaeche bot es nirgends an. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
46983a7e2c
commit
f678e988ac
@@ -119,6 +119,8 @@ export const LeadModal: React.FC<LeadModalProps> = ({
|
||||
const [newConnectionId, setNewConnectionId] = useState('');
|
||||
const [newConnectionType, setNewConnectionType] = useState<ConnectionType>('Netzwerk');
|
||||
const [isLinking, setIsLinking] = useState(false);
|
||||
/** Verbindung, deren Art gerade gespeichert wird - sperrt nur diese Zeile. */
|
||||
const [changingConnectionId, setChangingConnectionId] = useState<number | null>(null);
|
||||
|
||||
const leadById = useMemo(() => new Map(leads.map((l) => [l.id, l])), [leads]);
|
||||
|
||||
@@ -246,6 +248,24 @@ export const LeadModal: React.FC<LeadModalProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Die Art einer bestehenden Verbindung aendern. Das geht ohne Speichern-Knopf:
|
||||
* die Verbindung haengt nicht am Formular, sondern ist ein eigener Datensatz -
|
||||
* genau wie das Anlegen und das Entfernen daneben.
|
||||
*/
|
||||
const handleChangeConnectionType = async (id: number, type: ConnectionType) => {
|
||||
setChangingConnectionId(id);
|
||||
try {
|
||||
await connectionsApi.update(id, { type });
|
||||
addToast('Verbindung geändert.', 'success');
|
||||
onSaved();
|
||||
} catch (err) {
|
||||
addToast(err instanceof Error ? err.message : 'Ändern fehlgeschlagen.', 'error');
|
||||
} finally {
|
||||
setChangingConnectionId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveConnection = async (id: number) => {
|
||||
try {
|
||||
await connectionsApi.remove(id);
|
||||
@@ -572,10 +592,31 @@ export const LeadModal: React.FC<LeadModalProps> = ({
|
||||
<Avatar src={other!.avatar} initials={leadInitials(other!)} size="sm" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-bold text-ink-900">{leadFullName(other!)}</p>
|
||||
<p className="truncate text-xs text-ink-400">
|
||||
{connection.type}
|
||||
{other!.company ? ` · ${other!.company}` : ''}
|
||||
</p>
|
||||
<div className="mt-0.5 flex items-center gap-2">
|
||||
{/*
|
||||
* Die Art ist hier direkt umstellbar - vorher musste man die
|
||||
* Verbindung loeschen und neu anlegen, nur um "Kollege" in
|
||||
* "Vorgesetzter" zu aendern.
|
||||
*/}
|
||||
<select
|
||||
value={connection.type}
|
||||
disabled={changingConnectionId === connection.id}
|
||||
onChange={(e) =>
|
||||
handleChangeConnectionType(connection.id, e.target.value as ConnectionType)
|
||||
}
|
||||
aria-label={`Art der Verbindung zu ${leadFullName(other!)}`}
|
||||
className="max-w-[10rem] rounded-lg border border-ink-200 bg-surface px-2 py-1 text-xs font-bold text-ink-600 transition hover:border-brand-300 focus:border-brand-400 focus:outline-none disabled:opacity-50"
|
||||
>
|
||||
{CONNECTION_TYPES.map((type) => (
|
||||
<option key={type} value={type}>
|
||||
{type}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{other!.company && (
|
||||
<span className="truncate text-xs text-ink-400">{other!.company}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -95,6 +95,8 @@ export const LeadNetwork: React.FC<LeadNetworkProps> = ({
|
||||
const [linkMode, setLinkMode] = useState(false);
|
||||
const [linkSourceId, setLinkSourceId] = useState<number | null>(null);
|
||||
const [linkType, setLinkType] = useState<ConnectionType>('Netzwerk');
|
||||
/** Verbindung, deren Art gerade gespeichert wird - sperrt nur diese Zeile. */
|
||||
const [changingConnectionId, setChangingConnectionId] = useState<number | null>(null);
|
||||
|
||||
const leadById = useMemo(() => new Map(leads.map((lead) => [lead.id, lead])), [leads]);
|
||||
|
||||
@@ -505,6 +507,20 @@ export const LeadNetwork: React.FC<LeadNetworkProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
/** Art einer bestehenden Verbindung umstellen - wie im Kontaktdialog. */
|
||||
const changeConnectionType = async (connectionId: number, type: ConnectionType) => {
|
||||
setChangingConnectionId(connectionId);
|
||||
try {
|
||||
await connectionsApi.update(connectionId, { type });
|
||||
addToast('Verbindung geändert.', 'success');
|
||||
reload();
|
||||
} catch (err) {
|
||||
addToast(err instanceof Error ? err.message : 'Ändern fehlgeschlagen.', 'error');
|
||||
} finally {
|
||||
setChangingConnectionId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const resetView = () => {
|
||||
setTransform({ x: 0, y: 0, k: 1 });
|
||||
};
|
||||
@@ -963,15 +979,28 @@ export const LeadNetwork: React.FC<LeadNetworkProps> = ({
|
||||
key={connection.id}
|
||||
className="flex items-center gap-2 rounded-xl bg-ink-50 px-3 py-2"
|
||||
>
|
||||
<button
|
||||
onClick={() => setSelectedId(nodeId('lead', other!.id))}
|
||||
className="min-w-0 flex-1 text-left"
|
||||
>
|
||||
<p className="truncate text-xs font-bold text-ink-800">{leadFullName(other!)}</p>
|
||||
<p className="truncate text-[10px] font-semibold uppercase tracking-wider text-ink-400">
|
||||
{connection.type}
|
||||
</p>
|
||||
</button>
|
||||
<div className="min-w-0 flex-1">
|
||||
<button
|
||||
onClick={() => setSelectedId(nodeId('lead', other!.id))}
|
||||
className="block w-full truncate text-left text-xs font-bold text-ink-800"
|
||||
>
|
||||
{leadFullName(other!)}
|
||||
</button>
|
||||
{/* Art direkt umstellbar - ohne den Umweg ueber Loeschen und neu Anlegen. */}
|
||||
<select
|
||||
value={connection.type}
|
||||
disabled={changingConnectionId === connection.id}
|
||||
onChange={(e) => changeConnectionType(connection.id, e.target.value as ConnectionType)}
|
||||
aria-label={`Art der Verbindung zu ${leadFullName(other!)}`}
|
||||
className="mt-1 w-full rounded-lg border border-ink-200 bg-surface px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-ink-500 transition hover:border-brand-300 focus:border-brand-400 focus:outline-none disabled:opacity-50"
|
||||
>
|
||||
{CONNECTION_TYPES.map((type) => (
|
||||
<option key={type} value={type}>
|
||||
{type}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => removeConnection(connection.id)}
|
||||
className="shrink-0 rounded-lg p-1.5 text-ink-400 transition hover:bg-red-50 hover:text-red-600"
|
||||
|
||||
Reference in New Issue
Block a user