feat: add email recipient history to user settings and implement management in InboxDetailPage
Build and Push Multi-Platform Images / build-and-push (push) Successful in 36s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 36s
This commit is contained in:
@@ -31,4 +31,7 @@ export class UserSettings {
|
|||||||
|
|
||||||
@Column({ type: 'int', nullable: true })
|
@Column({ type: 'int', nullable: true })
|
||||||
DefaultLabelTemplateId!: number | null;
|
DefaultLabelTemplateId!: number | null;
|
||||||
|
|
||||||
|
@Column({ type: 'json', nullable: true })
|
||||||
|
EmailRecipientHistory!: string[] | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export interface UserSettingsDto {
|
|||||||
smtpFromName: string | null;
|
smtpFromName: string | null;
|
||||||
mailSignatureHtml: string | null;
|
mailSignatureHtml: string | null;
|
||||||
defaultLabelTemplateId: number | null;
|
defaultLabelTemplateId: number | null;
|
||||||
|
emailRecipientHistory: string[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -80,6 +81,7 @@ export class UserSettingsService {
|
|||||||
smtpFromName?: string | null;
|
smtpFromName?: string | null;
|
||||||
mailSignatureHtml?: string | null;
|
mailSignatureHtml?: string | null;
|
||||||
defaultLabelTemplateId?: number | null;
|
defaultLabelTemplateId?: number | null;
|
||||||
|
emailRecipientHistory?: string[] | null;
|
||||||
},
|
},
|
||||||
): Promise<UserSettingsDto> {
|
): Promise<UserSettingsDto> {
|
||||||
let entity = await this.repo.findOne({ where: { UserId: userId } });
|
let entity = await this.repo.findOne({ where: { UserId: userId } });
|
||||||
@@ -98,6 +100,7 @@ export class UserSettingsService {
|
|||||||
if (data.smtpFromName !== undefined) entity.SmtpFromName = data.smtpFromName;
|
if (data.smtpFromName !== undefined) entity.SmtpFromName = data.smtpFromName;
|
||||||
if (data.mailSignatureHtml !== undefined) entity.MailSignatureHtml = data.mailSignatureHtml;
|
if (data.mailSignatureHtml !== undefined) entity.MailSignatureHtml = data.mailSignatureHtml;
|
||||||
if (data.defaultLabelTemplateId !== undefined) entity.DefaultLabelTemplateId = data.defaultLabelTemplateId;
|
if (data.defaultLabelTemplateId !== undefined) entity.DefaultLabelTemplateId = data.defaultLabelTemplateId;
|
||||||
|
if (data.emailRecipientHistory !== undefined) entity.EmailRecipientHistory = data.emailRecipientHistory;
|
||||||
|
|
||||||
await this.repo.save(entity);
|
await this.repo.save(entity);
|
||||||
return this.toDto(entity);
|
return this.toDto(entity);
|
||||||
@@ -168,6 +171,7 @@ export class UserSettingsService {
|
|||||||
smtpFromName: entity?.SmtpFromName ?? null,
|
smtpFromName: entity?.SmtpFromName ?? null,
|
||||||
mailSignatureHtml: entity?.MailSignatureHtml ?? null,
|
mailSignatureHtml: entity?.MailSignatureHtml ?? null,
|
||||||
defaultLabelTemplateId: entity?.DefaultLabelTemplateId ?? null,
|
defaultLabelTemplateId: entity?.DefaultLabelTemplateId ?? null,
|
||||||
|
emailRecipientHistory: entity?.EmailRecipientHistory ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export interface UserSettingsData {
|
|||||||
smtpFromName: string | null;
|
smtpFromName: string | null;
|
||||||
mailSignatureHtml: string | null;
|
mailSignatureHtml: string | null;
|
||||||
defaultLabelTemplateId: number | null;
|
defaultLabelTemplateId: number | null;
|
||||||
|
emailRecipientHistory: string[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SenderOption {
|
export interface SenderOption {
|
||||||
|
|||||||
@@ -659,6 +659,7 @@ function SendEmailDialog({ open, fileId, fileName, documents, thumbUrls, onClose
|
|||||||
const [filenames, setFilenames] = useState<string[]>([]);
|
const [filenames, setFilenames] = useState<string[]>([]);
|
||||||
const [senders, setSenders] = useState<SenderOption[]>([]);
|
const [senders, setSenders] = useState<SenderOption[]>([]);
|
||||||
const [selectedSender, setSelectedSender] = useState<string>('default');
|
const [selectedSender, setSelectedSender] = useState<string>('default');
|
||||||
|
const [recipientHistory, setRecipientHistory] = useState<string[]>([]);
|
||||||
const editorRef = useRef<WysiwygEditorHandle>(null);
|
const editorRef = useRef<WysiwygEditorHandle>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -672,6 +673,7 @@ function SendEmailDialog({ open, fileId, fileName, documents, thumbUrls, onClose
|
|||||||
);
|
);
|
||||||
userSettingsApi.get().then((settings) => {
|
userSettingsApi.get().then((settings) => {
|
||||||
editorRef.current?.setContent(settings.mailSignatureHtml ?? '');
|
editorRef.current?.setContent(settings.mailSignatureHtml ?? '');
|
||||||
|
setRecipientHistory(settings.emailRecipientHistory ?? []);
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
userSettingsApi.getSenders().then((s) => {
|
userSettingsApi.getSenders().then((s) => {
|
||||||
setSenders(s);
|
setSenders(s);
|
||||||
@@ -679,12 +681,21 @@ function SendEmailDialog({ open, fileId, fileName, documents, thumbUrls, onClose
|
|||||||
}).catch(() => setSenders([]));
|
}).catch(() => setSenders([]));
|
||||||
}, [open, documents, fileName, form]);
|
}, [open, documents, fileName, form]);
|
||||||
|
|
||||||
|
const removeRecipient = (addr: string, e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const updated = recipientHistory.filter((a) => a !== addr);
|
||||||
|
setRecipientHistory(updated);
|
||||||
|
userSettingsApi.update({ emailRecipientHistory: updated }).catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
const handleOk = async () => {
|
const handleOk = async () => {
|
||||||
try {
|
try {
|
||||||
const values = await form.validateFields();
|
const values = await form.validateFields();
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
const addresses: string[] = values.to ?? [];
|
||||||
await inboxApi.sendEmail(fileId, {
|
await inboxApi.sendEmail(fileId, {
|
||||||
to: values.to,
|
to: addresses.join(', '),
|
||||||
subject: values.subject,
|
subject: values.subject,
|
||||||
body: editorRef.current?.getText() ?? '',
|
body: editorRef.current?.getText() ?? '',
|
||||||
html: editorRef.current?.getHTML(),
|
html: editorRef.current?.getHTML(),
|
||||||
@@ -694,6 +705,12 @@ function SendEmailDialog({ open, fileId, fileName, documents, thumbUrls, onClose
|
|||||||
})),
|
})),
|
||||||
sender: senders.length > 1 ? selectedSender : undefined,
|
sender: senders.length > 1 ? selectedSender : undefined,
|
||||||
});
|
});
|
||||||
|
const updated = [
|
||||||
|
...addresses,
|
||||||
|
...recipientHistory.filter((a) => !addresses.includes(a)),
|
||||||
|
].slice(0, 20);
|
||||||
|
setRecipientHistory(updated);
|
||||||
|
userSettingsApi.update({ emailRecipientHistory: updated }).catch(() => {});
|
||||||
message.success('E-Mail wurde gesendet');
|
message.success('E-Mail wurde gesendet');
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
@@ -726,8 +743,36 @@ function SendEmailDialog({ open, fileId, fileName, documents, thumbUrls, onClose
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
<Form.Item name="to" label="Empfänger" rules={[{ required: true, message: 'Bitte Empfänger angeben' }, { type: 'email', message: 'Ungültige E-Mail-Adresse' }]}>
|
<Form.Item
|
||||||
<Input placeholder="empfaenger@beispiel.de" />
|
name="to"
|
||||||
|
label="Empfänger"
|
||||||
|
rules={[{
|
||||||
|
validator: (_: unknown, value: string[]) => {
|
||||||
|
if (!value?.length) return Promise.reject('Bitte mindestens einen Empfänger angeben');
|
||||||
|
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
|
const bad = value.find((v) => !re.test(v));
|
||||||
|
return bad ? Promise.reject(`Ungültige Adresse: ${bad}`) : Promise.resolve();
|
||||||
|
},
|
||||||
|
}]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
mode="tags"
|
||||||
|
placeholder="empfaenger@beispiel.de"
|
||||||
|
tokenSeparators={[',', ';']}
|
||||||
|
options={recipientHistory.map((addr) => ({ value: addr, label: addr }))}
|
||||||
|
optionRender={(option) => (
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 }}>
|
||||||
|
<span>{option.label}</span>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={(e) => removeRecipient(option.value as string, e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
notFoundContent={<span style={{ color: '#999', fontSize: 12 }}>Kein Verlauf vorhanden</span>}
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="subject" label="Betreff" rules={[{ required: true, message: 'Bitte Betreff angeben' }]}>
|
<Form.Item name="subject" label="Betreff" rules={[{ required: true, message: 'Bitte Betreff angeben' }]}>
|
||||||
<Input />
|
<Input />
|
||||||
|
|||||||
Reference in New Issue
Block a user