a3a5a10514
- Workflows UI: full page with definitions/instances tabs, step editor, instance detail with approve/reject - Dedup/Merge UI: duplicate detection, side-by-side comparison, field-level merge dialog, merge history - Import/Export UI: import wizard (dry-run preview), export panel (CSV/XLSX), backend export route added - Print/PDF: PrintButton component, print.css, integrated in Contacts/Calendar/Reports/ContactDetail - Backend: GET /api/v1/export endpoint, export_companies_csv() service function - Routes: /workflows, /contacts/dedup, /import-export registered - Menu items: Workflows, Import/Export, Duplikate added to automation plugin manifest - IMPLEMENTATION_PLAN.md: audit-corrected plan for all 14 remaining features
138 lines
4.3 KiB
TypeScript
138 lines
4.3 KiB
TypeScript
/**
|
|
* PrintButton — reusable button with dropdown for Print and PDF export.
|
|
* Uses lucide-react icons and clsx for styling.
|
|
*/
|
|
|
|
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
import clsx from 'clsx';
|
|
import { Printer, FileDown, ChevronDown } from 'lucide-react';
|
|
import { printElement, printCurrentPage, exportToPDF } from '@/utils/print';
|
|
|
|
export interface PrintButtonProps {
|
|
/** Element id to print. If omitted, prints the whole page. */
|
|
targetId?: string;
|
|
/** Filename suggestion for PDF export. */
|
|
filename?: string;
|
|
/** Additional Tailwind classes for the trigger button. */
|
|
className?: string;
|
|
}
|
|
|
|
export function PrintButton({
|
|
targetId,
|
|
filename = 'export',
|
|
className,
|
|
}: PrintButtonProps) {
|
|
const [open, setOpen] = useState(false);
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
|
|
// Close dropdown when clicking outside
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
const handleClickOutside = (e: MouseEvent) => {
|
|
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
|
|
setOpen(false);
|
|
}
|
|
};
|
|
const handleEscape = (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') setOpen(false);
|
|
};
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
document.addEventListener('keydown', handleEscape);
|
|
return () => {
|
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
document.removeEventListener('keydown', handleEscape);
|
|
};
|
|
}, [open]);
|
|
|
|
const handlePrint = useCallback(() => {
|
|
setOpen(false);
|
|
if (targetId) {
|
|
printElement(targetId);
|
|
} else {
|
|
printCurrentPage();
|
|
}
|
|
}, [targetId]);
|
|
|
|
const handleExportPDF = useCallback(() => {
|
|
setOpen(false);
|
|
if (targetId) {
|
|
exportToPDF(targetId, filename);
|
|
} else {
|
|
// No specific element — use whole-page print which user can save as PDF
|
|
printCurrentPage();
|
|
}
|
|
}, [targetId, filename]);
|
|
|
|
return (
|
|
<div ref={containerRef} className="relative inline-block" data-no-print>
|
|
<button
|
|
type="button"
|
|
onClick={() => setOpen((v) => !v)}
|
|
className={clsx(
|
|
'inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-md',
|
|
'text-sm font-medium text-secondary-700',
|
|
'border border-secondary-300 bg-white',
|
|
'hover:bg-secondary-50 transition-colors',
|
|
'min-h-touch',
|
|
open && 'ring-2 ring-primary-500 ring-offset-1',
|
|
className,
|
|
)}
|
|
aria-haspopup="menu"
|
|
aria-expanded={open}
|
|
aria-label="Drucken oder als PDF exportieren"
|
|
title="Drucken / PDF"
|
|
data-testid="print-button-trigger"
|
|
>
|
|
<Printer className="w-4 h-4" aria-hidden="true" strokeWidth={2} />
|
|
<span className="hidden sm:inline">Drucken</span>
|
|
<ChevronDown
|
|
className={clsx('w-3.5 h-3.5 transition-transform', open && 'rotate-180')}
|
|
aria-hidden="true"
|
|
strokeWidth={2}
|
|
/>
|
|
</button>
|
|
|
|
{open && (
|
|
<div
|
|
role="menu"
|
|
className={clsx(
|
|
'absolute right-0 mt-1 z-50',
|
|
'min-w-[180px] rounded-md border border-secondary-200',
|
|
'bg-white shadow-lg py-1',
|
|
)}
|
|
data-testid="print-button-dropdown"
|
|
>
|
|
<button
|
|
type="button"
|
|
onClick={handlePrint}
|
|
className={clsx(
|
|
'flex items-center gap-2 w-full px-3 py-2',
|
|
'text-sm text-secondary-700 hover:bg-secondary-50',
|
|
'transition-colors text-left',
|
|
)}
|
|
role="menuitem"
|
|
data-testid="print-button-print"
|
|
>
|
|
<Printer className="w-4 h-4 text-secondary-500" aria-hidden="true" strokeWidth={2} />
|
|
<span>Drucken</span>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={handleExportPDF}
|
|
className={clsx(
|
|
'flex items-center gap-2 w-full px-3 py-2',
|
|
'text-sm text-secondary-700 hover:bg-secondary-50',
|
|
'transition-colors text-left',
|
|
)}
|
|
role="menuitem"
|
|
data-testid="print-button-pdf"
|
|
>
|
|
<FileDown className="w-4 h-4 text-secondary-500" aria-hidden="true" strokeWidth={2} />
|
|
<span>Als PDF</span>
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|