fix: resolve all 12 TypeScript errors (tsc --noEmit clean)
- mail.ts: add explicit types to decodeMimeHeader callback params - SessionList.tsx: use React.MouseEvent instead of nativeEvent - ComposeModal.tsx: construct name from first_name + last_name - MailDetail.tsx: destructure onReply/onForward from props - RichTextEditor.tsx: use SetContentOptions object instead of boolean - MailSearchBar.tsx: add optional value prop to interface - EmptyState.tsx: add optional children prop - Badge.tsx: add secondary variant to BadgeVariant, variantClasses, dotColors - ConfirmDialog.tsx: add optional children prop - hooks.ts: extend SearchResult type to include mail/file/event
This commit is contained in:
@@ -67,7 +67,7 @@ export interface AuditLogEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchResult {
|
export interface SearchResult {
|
||||||
type: 'company' | 'contact';
|
type: 'company' | 'contact' | 'mail' | 'file' | 'event';
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|||||||
@@ -417,7 +417,7 @@ export function decodeMimeHeader(value: string | undefined | null): string {
|
|||||||
return decodeURIComponent(escape(decoded));
|
return decodeURIComponent(escape(decoded));
|
||||||
} else {
|
} else {
|
||||||
// Q encoding: replace _ with space, then decode =XX hex sequences
|
// Q encoding: replace _ with space, then decode =XX hex sequences
|
||||||
const qDecoded = encoded.replace(/_/g, ' ').replace(/=([0-9A-F]{2})/gi, (_m, hex) => {
|
const qDecoded = encoded.replace(/_/g, ' ').replace(/=([0-9A-F]{2})/gi, (_m: string, hex: string) => {
|
||||||
return String.fromCharCode(parseInt(hex, 16));
|
return String.fromCharCode(parseInt(hex, 16));
|
||||||
});
|
});
|
||||||
return decodeURIComponent(escape(qDecoded));
|
return decodeURIComponent(escape(qDecoded));
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ function TreeItem({
|
|||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span className="flex-1 truncate">{node.folder!.name}</span>
|
<span className="flex-1 truncate">{node.folder!.name}</span>
|
||||||
<button onClick={(e) => { e.stopPropagation(); onContextMenu(e.nativeEvent, 'folder', node.folder!.id); }} className="opacity-0 group-hover:opacity-100 text-secondary-400 hover:text-primary-600 p-0.5" title="Umbenennen">
|
<button onClick={(e) => { e.stopPropagation(); onContextMenu(e, 'folder', node.folder!.id); }} className="opacity-0 group-hover:opacity-100 text-secondary-400 hover:text-primary-600 p-0.5" title="Umbenennen">
|
||||||
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /></svg>
|
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /></svg>
|
||||||
</button>
|
</button>
|
||||||
<button onClick={(e) => { e.stopPropagation(); onDelete(node.folder!.id, 'folder'); }} className="opacity-0 group-hover:opacity-100 text-secondary-400 hover:text-red-600 p-0.5" title="Löschen">
|
<button onClick={(e) => { e.stopPropagation(); onDelete(node.folder!.id, 'folder'); }} className="opacity-0 group-hover:opacity-100 text-secondary-400 hover:text-red-600 p-0.5" title="Löschen">
|
||||||
@@ -131,7 +131,7 @@ function TreeItem({
|
|||||||
<span className={clsx('flex-1 truncate', activeSessionId === session.id && 'font-bold text-primary-700')}>
|
<span className={clsx('flex-1 truncate', activeSessionId === session.id && 'font-bold text-primary-700')}>
|
||||||
{session.title}
|
{session.title}
|
||||||
</span>
|
</span>
|
||||||
<button onClick={(e) => { e.stopPropagation(); onContextMenu(e.nativeEvent, 'session', session.id); }} className="opacity-0 group-hover:opacity-100 text-secondary-400 hover:text-primary-600 p-0.5" title="Umbenennen">
|
<button onClick={(e) => { e.stopPropagation(); onContextMenu(e, 'session', session.id); }} className="opacity-0 group-hover:opacity-100 text-secondary-400 hover:text-primary-600 p-0.5" title="Umbenennen">
|
||||||
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /></svg>
|
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /></svg>
|
||||||
</button>
|
</button>
|
||||||
<button onClick={(e) => { e.stopPropagation(); onDelete(session.id, 'session'); }} className="opacity-0 group-hover:opacity-100 text-secondary-400 hover:text-red-600 p-0.5" title="Löschen">
|
<button onClick={(e) => { e.stopPropagation(); onDelete(session.id, 'session'); }} className="opacity-0 group-hover:opacity-100 text-secondary-400 hover:text-red-600 p-0.5" title="Löschen">
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export function ComposeModal({
|
|||||||
if (sig) {
|
if (sig) {
|
||||||
const processedHtml = replaceSignatureVariables(
|
const processedHtml = replaceSignatureVariables(
|
||||||
sig.body_html,
|
sig.body_html,
|
||||||
{ name: authUser?.name, email: authUser?.email, role: authUser?.role, first_name: authUser?.first_name, last_name: authUser?.last_name },
|
{ name: authUser ? `${authUser.first_name} ${authUser.last_name}`.trim() : undefined, email: authUser?.email, role: authUser?.role, first_name: authUser?.first_name, last_name: authUser?.last_name },
|
||||||
{ name: authTenant?.name },
|
{ name: authTenant?.name },
|
||||||
);
|
);
|
||||||
setBody((prev) => `${prev}<br/><br/>---<br/>${processedHtml}`);
|
setBody((prev) => `${prev}<br/><br/>---<br/>${processedHtml}`);
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ function formatBytes(bytes: number): string {
|
|||||||
export function MailDetail({
|
export function MailDetail({
|
||||||
mail,
|
mail,
|
||||||
loading,
|
loading,
|
||||||
|
onReply,
|
||||||
|
onForward,
|
||||||
onDownloadAttachment,
|
onDownloadAttachment,
|
||||||
downloadingAttachmentId,
|
downloadingAttachmentId,
|
||||||
}: MailDetailProps) {
|
}: MailDetailProps) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Input } from '@/components/ui/Input';
|
|||||||
|
|
||||||
export interface MailSearchBarProps {
|
export interface MailSearchBarProps {
|
||||||
onSearch: (query: string) => void;
|
onSearch: (query: string) => void;
|
||||||
|
value?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MailSearchBar({ onSearch }: MailSearchBarProps) {
|
export function MailSearchBar({ onSearch }: MailSearchBarProps) {
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export function RichTextEditor({ content, onChange, placeholder, editable = true
|
|||||||
// Sync external content changes (e.g. template/signature insert, reply/forward/draft load)
|
// Sync external content changes (e.g. template/signature insert, reply/forward/draft load)
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (editor && content !== editor.getHTML()) {
|
if (editor && content !== editor.getHTML()) {
|
||||||
editor.commands.setContent(content, false);
|
editor.commands.setContent(content, { emitUpdate: false });
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [content, editor]);
|
}, [content, editor]);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
export type BadgeVariant = 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info';
|
export type BadgeVariant = 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'secondary';
|
||||||
|
|
||||||
export interface BadgeProps {
|
export interface BadgeProps {
|
||||||
variant?: BadgeVariant;
|
variant?: BadgeVariant;
|
||||||
@@ -17,6 +17,7 @@ const variantClasses: Record<BadgeVariant, string> = {
|
|||||||
warning: 'bg-warning-100 text-warning-700',
|
warning: 'bg-warning-100 text-warning-700',
|
||||||
danger: 'bg-danger-100 text-danger-700',
|
danger: 'bg-danger-100 text-danger-700',
|
||||||
info: 'bg-accent-100 text-accent-700',
|
info: 'bg-accent-100 text-accent-700',
|
||||||
|
secondary: 'bg-secondary-200 text-secondary-800',
|
||||||
};
|
};
|
||||||
|
|
||||||
const dotColors: Record<BadgeVariant, string> = {
|
const dotColors: Record<BadgeVariant, string> = {
|
||||||
@@ -26,6 +27,7 @@ const dotColors: Record<BadgeVariant, string> = {
|
|||||||
warning: 'bg-warning-500',
|
warning: 'bg-warning-500',
|
||||||
danger: 'bg-danger-500',
|
danger: 'bg-danger-500',
|
||||||
info: 'bg-accent-500',
|
info: 'bg-accent-500',
|
||||||
|
secondary: 'bg-secondary-500',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Badge({ variant = 'default', children, className, dot = false }: BadgeProps) {
|
export function Badge({ variant = 'default', children, className, dot = false }: BadgeProps) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export interface ConfirmDialogProps {
|
|||||||
variant?: 'primary' | 'danger';
|
variant?: 'primary' | 'danger';
|
||||||
onConfirm: () => void;
|
onConfirm: () => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConfirmDialog({
|
export function ConfirmDialog({
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export interface EmptyStateProps {
|
|||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
action?: React.ReactNode;
|
action?: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EmptyState({
|
export function EmptyState({
|
||||||
|
|||||||
Reference in New Issue
Block a user