Suspects: Spalte Mail und Löschen direkt in der Tabelle
- email als Symbol mit Adresse im Tooltip, Klick öffnet mailto: (Spalte per migrateColumns nachgezogen) - Löschen-Knopf am Zeilenende; Server erlaubt Löschen jetzt auch ohne vorheriges Archivieren Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6a452f1510
commit
78d6bef52c
@@ -31,12 +31,13 @@ interface SuspectsProps {
|
||||
onChanged: () => void;
|
||||
}
|
||||
|
||||
type TextField = 'name' | 'role' | 'company' | 'linkedin' | 'website' | 'notes';
|
||||
type TextField = 'name' | 'role' | 'company' | 'email' | 'linkedin' | 'website' | 'notes';
|
||||
|
||||
const EMPTY_DRAFT: Record<TextField, string> & { last_contact_on: string } = {
|
||||
name: '',
|
||||
role: '',
|
||||
company: '',
|
||||
email: '',
|
||||
linkedin: '',
|
||||
website: '',
|
||||
notes: '',
|
||||
@@ -161,23 +162,25 @@ export const Suspects: React.FC<SuspectsProps> = ({ archived, companies, contact
|
||||
</p>
|
||||
|
||||
<div className="overflow-x-auto rounded-xl border border-ink-200 bg-surface">
|
||||
<table className="w-full min-w-[1100px] table-fixed text-sm">
|
||||
<table className="w-full min-w-[1200px] table-fixed text-sm">
|
||||
<colgroup>
|
||||
<col className="w-[15%]" />
|
||||
<col className="w-[14%]" />
|
||||
<col className="w-[14%]" />
|
||||
<col className="w-[64px]" />
|
||||
<col className="w-[72px]" />
|
||||
<col className="w-[72px]" />
|
||||
<col />
|
||||
<col className="w-[130px]" />
|
||||
<col className="w-[120px]" />
|
||||
<col className="w-[96px]" />
|
||||
<col className="w-[128px]" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr className="border-b border-ink-200 text-left text-[11px] font-semibold uppercase tracking-wider text-ink-500">
|
||||
<th className="px-3 py-3">Name</th>
|
||||
<th className="px-3 py-3">Funktion</th>
|
||||
<th className="px-3 py-3">Unternehmen</th>
|
||||
<th className="px-3 py-3 text-center">Mail</th>
|
||||
<th className="px-3 py-3 text-center">LinkedIn</th>
|
||||
<th className="px-3 py-3 text-center">Website</th>
|
||||
<th className="px-3 py-3">Notiz</th>
|
||||
@@ -206,6 +209,9 @@ export const Suspects: React.FC<SuspectsProps> = ({ archived, companies, contact
|
||||
/>
|
||||
</td>
|
||||
))}
|
||||
<td className="px-1 py-1 text-center">
|
||||
<LinkCell value={row.email} kind="mail" onCommit={(value) => void update(row, { email: value })} />
|
||||
</td>
|
||||
<td className="px-1 py-1 text-center">
|
||||
<LinkCell value={row.linkedin} kind="linkedin" onCommit={(value) => void update(row, { linkedin: value })} />
|
||||
</td>
|
||||
@@ -231,6 +237,7 @@ export const Suspects: React.FC<SuspectsProps> = ({ archived, companies, contact
|
||||
<div className="flex justify-end gap-1">
|
||||
<IconButton title="Archivieren" onClick={() => void archive(row)} path={ARCHIVE_ICON} />
|
||||
<IconButton title="Lead erstellen" onClick={() => setToLead(row)} path={LEAD_ICON} tone="brand" />
|
||||
<IconButton title="Löschen" onClick={() => setRemoving(row)} path={DELETE_ICON} tone="red" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -251,7 +258,7 @@ export const Suspects: React.FC<SuspectsProps> = ({ archived, companies, contact
|
||||
}
|
||||
}}
|
||||
>
|
||||
{(['name', 'role', 'company', 'linkedin', 'website', 'notes'] as const).map((field) => (
|
||||
{(['name', 'role', 'company', 'email', 'linkedin', 'website', 'notes'] as const).map((field) => (
|
||||
<td key={field} className="px-1 py-1">
|
||||
<input
|
||||
ref={field === 'name' ? draftName : undefined}
|
||||
@@ -278,7 +285,7 @@ export const Suspects: React.FC<SuspectsProps> = ({ archived, companies, contact
|
||||
|
||||
{archived && rows.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={9} className="py-12 text-center text-sm text-ink-400">
|
||||
<td colSpan={10} className="py-12 text-center text-sm text-ink-400">
|
||||
Das Archiv ist leer.
|
||||
</td>
|
||||
</tr>
|
||||
@@ -328,6 +335,7 @@ const DRAFT_PLACEHOLDERS: Record<TextField, string> = {
|
||||
name: '+ Name',
|
||||
role: 'Funktion',
|
||||
company: 'Unternehmen',
|
||||
email: 'Mail',
|
||||
linkedin: 'LinkedIn-URL',
|
||||
website: 'Website',
|
||||
notes: 'Notiz',
|
||||
@@ -372,8 +380,16 @@ const TextCell: React.FC<{ value: string | null; required?: boolean; onCommit: (
|
||||
);
|
||||
};
|
||||
|
||||
type LinkKind = 'linkedin' | 'website' | 'mail';
|
||||
|
||||
const LINK_KINDS: Record<LinkKind, { color: string; href: (value: string) => string; placeholder: string }> = {
|
||||
linkedin: { color: 'text-[#0a66c2]', href: (v) => v, placeholder: 'URL' },
|
||||
website: { color: 'text-brand-600', href: (v) => v, placeholder: 'URL' },
|
||||
mail: { color: 'text-brand-600', href: (v) => `mailto:${v}`, placeholder: 'Mail' },
|
||||
};
|
||||
|
||||
/** Link als Symbol; die Adresse zeigt der Tooltip. Bearbeiten über den Stift. */
|
||||
const LinkCell: React.FC<{ value: string | null; kind: 'linkedin' | 'website'; onCommit: (value: string | null) => void }> = ({
|
||||
const LinkCell: React.FC<{ value: string | null; kind: LinkKind; onCommit: (value: string | null) => void }> = ({
|
||||
value,
|
||||
kind,
|
||||
onCommit,
|
||||
@@ -399,7 +415,8 @@ const LinkCell: React.FC<{ value: string | null; kind: 'linkedin' | 'website'; o
|
||||
setEditing(false);
|
||||
}
|
||||
}}
|
||||
placeholder="URL"
|
||||
type={kind === 'mail' ? 'email' : 'text'}
|
||||
placeholder={LINK_KINDS[kind].placeholder}
|
||||
className={`${cellInput} text-xs`}
|
||||
/>
|
||||
);
|
||||
@@ -407,23 +424,11 @@ const LinkCell: React.FC<{ value: string | null; kind: 'linkedin' | 'website'; o
|
||||
|
||||
return (
|
||||
<span className="group inline-flex items-center gap-0.5">
|
||||
<a
|
||||
href={value}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={value}
|
||||
className={`inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-ink-100 ${
|
||||
kind === 'linkedin' ? 'text-[#0a66c2]' : 'text-brand-600'
|
||||
}`}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" className="h-4 w-4" fill="currentColor" aria-hidden="true">
|
||||
<path d={kind === 'linkedin' ? LINKEDIN_ICON : LINK_ICON} />
|
||||
</svg>
|
||||
</a>
|
||||
<StaticLink href={value} kind={kind} />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setEditing(true)}
|
||||
title="Link bearbeiten"
|
||||
title={kind === 'mail' ? 'Mail bearbeiten' : 'Link bearbeiten'}
|
||||
className="invisible rounded p-0.5 text-ink-400 hover:text-ink-700 group-hover:visible"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" className="h-3 w-3" fill="currentColor" aria-hidden="true">
|
||||
@@ -534,6 +539,7 @@ const ArchivedRow: React.FC<{ row: Suspect; onRestore: () => void; onDelete: ()
|
||||
</td>
|
||||
<td className="truncate px-3 py-2.5 text-ink-600">{row.role ?? '–'}</td>
|
||||
<td className="truncate px-3 py-2.5 text-ink-600">{row.company ?? '–'}</td>
|
||||
<td className="px-3 py-2.5 text-center">{row.email && <StaticLink href={row.email} kind="mail" />}</td>
|
||||
<td className="px-3 py-2.5 text-center">{row.linkedin && <StaticLink href={row.linkedin} kind="linkedin" />}</td>
|
||||
<td className="px-3 py-2.5 text-center">{row.website && <StaticLink href={row.website} kind="website" />}</td>
|
||||
<td className="truncate px-3 py-2.5 text-ink-600" title={row.notes ?? undefined}>
|
||||
@@ -550,18 +556,16 @@ const ArchivedRow: React.FC<{ row: Suspect; onRestore: () => void; onDelete: ()
|
||||
</tr>
|
||||
);
|
||||
|
||||
const StaticLink: React.FC<{ href: string; kind: 'linkedin' | 'website' }> = ({ href, kind }) => (
|
||||
const StaticLink: React.FC<{ href: string; kind: LinkKind }> = ({ href, kind }) => (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
href={LINK_KINDS[kind].href(href)}
|
||||
target={kind === 'mail' ? undefined : '_blank'}
|
||||
rel="noopener noreferrer"
|
||||
title={href}
|
||||
className={`inline-flex h-7 w-7 items-center justify-center rounded-md hover:bg-ink-100 ${
|
||||
kind === 'linkedin' ? 'text-[#0a66c2]' : 'text-brand-600'
|
||||
}`}
|
||||
className={`inline-flex h-8 w-8 items-center justify-center rounded-md hover:bg-ink-100 ${LINK_KINDS[kind].color}`}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" className="h-4 w-4" fill="currentColor" aria-hidden="true">
|
||||
<path d={kind === 'linkedin' ? LINKEDIN_ICON : LINK_ICON} />
|
||||
<path d={kind === 'linkedin' ? LINKEDIN_ICON : kind === 'mail' ? MAIL_ICON : LINK_ICON} />
|
||||
</svg>
|
||||
</a>
|
||||
);
|
||||
@@ -607,6 +611,7 @@ function leadFromSuspect(row: Suspect, companies: Company[], contacts: Contact[]
|
||||
|
||||
const notes = [
|
||||
`Aus Suspects: ${row.name}${row.role ? `, ${row.role}` : ''}${row.company ? ` (${row.company})` : ''}`,
|
||||
row.email ? `Mail: ${row.email}` : null,
|
||||
row.linkedin ? `LinkedIn: ${row.linkedin}` : null,
|
||||
row.website ? `Website: ${row.website}` : null,
|
||||
row.last_contact_on ? `Letzter Kontakt: ${formatDate(row.last_contact_on)}` : null,
|
||||
@@ -634,5 +639,6 @@ const ALARM_ICON =
|
||||
'M12 5a8 8 0 1 1 0 16 8 8 0 0 1 0-16Zm0 2a6 6 0 1 0 0 12 6 6 0 0 0 0-12Zm-1 2h2v4l3 2-1 1.6-4-2.6V9ZM6.3 2.3l1.4 1.4-3.9 3.9-1.4-1.4 3.9-3.9Zm11.4 0 3.9 3.9-1.4 1.4-3.9-3.9 1.4-1.4Z';
|
||||
const LINK_ICON =
|
||||
'M10.6 13.4a1 1 0 0 1 0-1.4l3.5-3.5a1 1 0 1 1 1.4 1.4L12 13.4a1 1 0 0 1-1.4 0ZM8.5 11.3 6.4 13.4a3 3 0 0 0 4.2 4.2l2.1-2.1 1.4 1.4-2.1 2.1a5 5 0 0 1-7-7l2.1-2.1 1.4 1.4Zm7-5.7a3 3 0 0 0-4.2 0L9.2 7.7 7.8 6.3l2.1-2.1a5 5 0 0 1 7 7l-2.1 2.1-1.4-1.4 2.1-2.1a3 3 0 0 0 0-4.2Z';
|
||||
const MAIL_ICON = 'M3 5h18v14H3V5Zm2 2v.4l7 4.6 7-4.6V7H5Zm14 2.8-7 4.6-7-4.6V17h14V9.8Z';
|
||||
const LINKEDIN_ICON =
|
||||
'M4 3h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Zm3.3 7H5v8h2.3v-8ZM6.2 5.8a1.3 1.3 0 1 0 0 2.7 1.3 1.3 0 0 0 0-2.7ZM19 13.4c0-2.2-1.2-3.5-3.1-3.5-1.1 0-1.9.6-2.2 1.1V10h-2.2v8h2.3v-4.2c0-1.1.5-1.8 1.4-1.8.8 0 1.3.6 1.3 1.8V18H19v-4.6Z';
|
||||
|
||||
+2
-1
@@ -440,6 +440,7 @@ export interface Suspect {
|
||||
name: string;
|
||||
role: string | null;
|
||||
company: string | null;
|
||||
email: string | null;
|
||||
linkedin: string | null;
|
||||
website: string | null;
|
||||
notes: string | null;
|
||||
@@ -454,7 +455,7 @@ export interface Suspect {
|
||||
}
|
||||
|
||||
export type SuspectInput = Partial<
|
||||
Pick<Suspect, 'name' | 'role' | 'company' | 'linkedin' | 'website' | 'notes' | 'last_contact_on' | 'archived' | 'lead_id'>
|
||||
Pick<Suspect, 'name' | 'role' | 'company' | 'email' | 'linkedin' | 'website' | 'notes' | 'last_contact_on' | 'archived' | 'lead_id'>
|
||||
> & { remind_in_days?: number | null };
|
||||
|
||||
export const suspectsApi = {
|
||||
|
||||
Reference in New Issue
Block a user