feat: implement US-3 - enhance print layout with professional table design
- Add comprehensive i18n support for all print preview content - Fix next-intl context error by moving translations to server component - Implement professional 3-column table layout with proper styling - Add multilingual table headers (EN: #, Bill Information, 2D Barcode | HR: #, Informacije o Računu, 2D Barkod) - Center-align index column header and optimize barcode sizing - Hide print button and header from actual print output - Fix hydration errors with proper date handling - Enhanced barcode display with proper padding and sizing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { fetchBarcodeDataForPrint } from '@/app/lib/actions/printActions';
|
import { fetchBarcodeDataForPrint } from '@/app/lib/actions/printActions';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import { PrintPreview } from '@/app/ui/PrintPreview';
|
import { PrintPreview } from '@/app/ui/PrintPreview';
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
interface PrintPageProps {
|
interface PrintPageProps {
|
||||||
params: {
|
params: {
|
||||||
@@ -28,6 +29,19 @@ export default async function PrintPage({ params }: PrintPageProps) {
|
|||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get translations for the current locale
|
||||||
|
const t = await getTranslations("home-page.print-preview");
|
||||||
|
const translations = {
|
||||||
|
title: t("title"),
|
||||||
|
barcodesFound: t("barcodes-found"),
|
||||||
|
barcodeSingular: t("barcode-singular"),
|
||||||
|
printButton: t("print-button"),
|
||||||
|
printFooter: t("print-footer"),
|
||||||
|
tableHeaderIndex: t("table-header-index"),
|
||||||
|
tableHeaderBillInfo: t("table-header-bill-info"),
|
||||||
|
tableHeaderBarcode: t("table-header-barcode")
|
||||||
|
};
|
||||||
|
|
||||||
// If no barcode data found, show empty state
|
// If no barcode data found, show empty state
|
||||||
if (!printData || printData.length === 0) {
|
if (!printData || printData.length === 0) {
|
||||||
return (
|
return (
|
||||||
@@ -42,5 +56,5 @@ export default async function PrintPage({ params }: PrintPageProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <PrintPreview data={printData} year={year} month={month} />;
|
return <PrintPreview data={printData} year={year} month={month} translations={translations} />;
|
||||||
}
|
}
|
||||||
@@ -6,58 +6,93 @@ export interface PrintPreviewProps {
|
|||||||
data: PrintBarcodeData[];
|
data: PrintBarcodeData[];
|
||||||
year: number;
|
year: number;
|
||||||
month: number;
|
month: number;
|
||||||
|
translations: {
|
||||||
|
title: string;
|
||||||
|
barcodesFound: string;
|
||||||
|
barcodeSingular: string;
|
||||||
|
printButton: string;
|
||||||
|
printFooter: string;
|
||||||
|
tableHeaderIndex: string;
|
||||||
|
tableHeaderBillInfo: string;
|
||||||
|
tableHeaderBarcode: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PrintPreview: React.FC<PrintPreviewProps> = ({ data, year, month }) => {
|
export const PrintPreview: React.FC<PrintPreviewProps> = ({ data, year, month, translations }) => {
|
||||||
return (
|
|
||||||
<div className="min-h-screen p-4">
|
|
||||||
<h1 className="text-2xl font-bold mb-4">
|
|
||||||
Print Preview - {year}-{month.toString().padStart(2, '0')}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div className="mb-4">
|
return (
|
||||||
<p>Found {data.length} barcode(s) for printing</p>
|
<div className="min-h-screen bg-white">
|
||||||
|
{/* Header section - hidden in print */}
|
||||||
|
<div className="p-6 border-b border-gray-200 print:hidden">
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
||||||
|
{translations.title}
|
||||||
|
</h1>
|
||||||
|
<p className="text-lg text-gray-600 mb-4">
|
||||||
|
{year}-{month.toString().padStart(2, '0')} • {data.length} {data.length === 1 ? translations.barcodeSingular : translations.barcodesFound}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => window.print()}
|
||||||
|
className="btn btn-primary"
|
||||||
|
>
|
||||||
|
🖨️ {translations.printButton}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Basic table structure - will be enhanced in US-3 */}
|
{/* Print content */}
|
||||||
<table className="w-full border-collapse border border-gray-300">
|
<div className="p-6">
|
||||||
<thead>
|
<table className="w-full border-collapse border-2 border-gray-800">
|
||||||
<tr>
|
<thead>
|
||||||
<th className="border border-gray-300 p-2">Index</th>
|
<tr className="bg-gray-100">
|
||||||
<th className="border border-gray-300 p-2">Bill Info</th>
|
<th className="border-2 border-gray-800 px-3 py-2 text-center font-bold text-sm w-16">
|
||||||
<th className="border border-gray-300 p-2">Barcode</th>
|
{translations.tableHeaderIndex}
|
||||||
</tr>
|
</th>
|
||||||
</thead>
|
<th className="border-2 border-gray-800 px-3 py-2 text-left font-bold text-sm">
|
||||||
<tbody>
|
{translations.tableHeaderBillInfo}
|
||||||
{data.map((item, index) => (
|
</th>
|
||||||
<tr key={`${item.locationName}-${item.billName}`}>
|
<th className="border-2 border-gray-800 px-3 py-2 text-center font-bold text-sm w-64">
|
||||||
<td className="border border-gray-300 p-2">{index + 1}</td>
|
{translations.tableHeaderBarcode}
|
||||||
<td className="border border-gray-300 p-2">
|
</th>
|
||||||
<div>
|
|
||||||
<div className="font-medium">{item.yearMonth}</div>
|
|
||||||
<div>{item.locationName}</div>
|
|
||||||
<div>{item.billName}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="border border-gray-300 p-2">
|
|
||||||
<img
|
|
||||||
src={item.barcodeImage.startsWith('data:') ? item.barcodeImage : `data:image/png;base64,${item.barcodeImage}`}
|
|
||||||
alt={`Barcode for ${item.billName}`}
|
|
||||||
className="max-w-full h-auto"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{data.map((item, index) => (
|
||||||
|
<tr key={`${item.locationName}-${item.billName}`} className="hover:bg-gray-50">
|
||||||
|
<td className="border-2 border-gray-800 px-3 py-4 text-center font-mono text-sm font-medium">
|
||||||
|
{(index + 1).toString().padStart(2, '0')}
|
||||||
|
</td>
|
||||||
|
<td className="border-2 border-gray-800 px-3 py-4">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="font-bold text-sm text-gray-900">
|
||||||
|
📅 {item.yearMonth}
|
||||||
|
</div>
|
||||||
|
<div className="font-medium text-sm text-gray-800">
|
||||||
|
🏠 {item.locationName}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-700">
|
||||||
|
📋 {item.billName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="border-2 border-gray-800 px-3 py-1.5 text-center">
|
||||||
|
<div className="flex justify-center items-center">
|
||||||
|
<img
|
||||||
|
src={item.barcodeImage.startsWith('data:') ? item.barcodeImage : `data:image/png;base64,${item.barcodeImage}`}
|
||||||
|
alt={`Barcode for ${item.billName}`}
|
||||||
|
className="max-h-28 w-auto border border-gray-300 rounded"
|
||||||
|
style={{ maxWidth: '270px' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
{/* Print button - will be enhanced in US-4 */}
|
{/* Print footer - only visible when printing */}
|
||||||
<button
|
<div className="mt-6 text-center text-xs text-gray-500 hidden print:block">
|
||||||
onClick={() => window.print()}
|
<p>{translations.printFooter.replace('{date}', new Date().toLocaleDateString())}</p>
|
||||||
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
</div>
|
||||||
>
|
</div>
|
||||||
Print
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -61,6 +61,16 @@
|
|||||||
"payed-total-label": "Total monthly expenditure:",
|
"payed-total-label": "Total monthly expenditure:",
|
||||||
"print-codes-tooltip": "Print 2D codes",
|
"print-codes-tooltip": "Print 2D codes",
|
||||||
"print-codes-label": "Print codes"
|
"print-codes-label": "Print codes"
|
||||||
|
},
|
||||||
|
"print-preview": {
|
||||||
|
"title": "2D Barcode Print Preview",
|
||||||
|
"barcodes-found": "barcodes found",
|
||||||
|
"barcode-singular": "barcode found",
|
||||||
|
"print-button": "Print Barcodes",
|
||||||
|
"print-footer": "Generated on {date} • Evidencija Režija Print System",
|
||||||
|
"table-header-index": "#",
|
||||||
|
"table-header-bill-info": "Bill Information",
|
||||||
|
"table-header-barcode": "2D Barcode"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bill-delete-form": {
|
"bill-delete-form": {
|
||||||
|
|||||||
@@ -61,6 +61,16 @@
|
|||||||
"payed-total-label": "Ukupni mjesečni trošak:",
|
"payed-total-label": "Ukupni mjesečni trošak:",
|
||||||
"print-codes-tooltip": "Ispis 2d kodova",
|
"print-codes-tooltip": "Ispis 2d kodova",
|
||||||
"print-codes-label": "Ispis kodova"
|
"print-codes-label": "Ispis kodova"
|
||||||
|
},
|
||||||
|
"print-preview": {
|
||||||
|
"title": "Pregled Ispisa 2D Barkodova",
|
||||||
|
"barcodes-found": "barkodova pronađeno",
|
||||||
|
"barcode-singular": "barkod pronađen",
|
||||||
|
"print-button": "Ispis Barkodova",
|
||||||
|
"print-footer": "Generirano {date} • Evidencija Režija Sustav Ispisa",
|
||||||
|
"table-header-index": "#",
|
||||||
|
"table-header-bill-info": "Informacije o Računu",
|
||||||
|
"table-header-barcode": "2D Barkod"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bill-delete-form": {
|
"bill-delete-form": {
|
||||||
|
|||||||
Reference in New Issue
Block a user