Restructured the repository into a monorepo to better organize application code and maintenance scripts. ## Workspace Structure - web-app: Next.js application (all app code moved from root) - housekeeping: Database backup and maintenance scripts ## Key Changes - Moved all application code to web-app/ using git mv - Moved database scripts to housekeeping/ workspace - Updated Dockerfile for monorepo build process - Updated docker-compose files (volume paths: ./web-app/etc/hosts/) - Updated .gitignore for workspace-level node_modules - Updated documentation (README.md, CLAUDE.md, CHANGELOG.md) ## Migration Impact - Root package.json now manages workspaces - Build commands delegate to web-app workspace - All file history preserved via git mv - Docker build process updated for workspace structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
158 lines
5.0 KiB
TypeScript
158 lines
5.0 KiB
TypeScript
'use client';
|
||
|
||
import { PrintBarcodeData } from '../lib/actions/printActions';
|
||
import { Pdf417Barcode } from './Pdf417Barcode';
|
||
|
||
export interface PrintPreviewProps {
|
||
data: PrintBarcodeData[];
|
||
year: 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, translations }) => {
|
||
|
||
return (
|
||
<>
|
||
{/* Print-specific CSS styles */}
|
||
<style jsx global>{`
|
||
html {
|
||
background-color: white !important;
|
||
color-scheme: light !important;
|
||
}
|
||
`}</style>
|
||
<style jsx>{`
|
||
@media print {
|
||
@page {
|
||
size: A4;
|
||
margin: 0;
|
||
}
|
||
|
||
body {
|
||
-webkit-print-color-adjust: exact !important;
|
||
color-adjust: exact !important;
|
||
print-color-adjust: exact !important;
|
||
}
|
||
|
||
.print-table {
|
||
page-break-inside: avoid;
|
||
}
|
||
|
||
.print-table tr {
|
||
page-break-inside: avoid;
|
||
page-break-after: auto;
|
||
}
|
||
|
||
.print-table thead {
|
||
display: table-header-group;
|
||
}
|
||
|
||
/* Optimize for B&W printing */
|
||
* {
|
||
color: black !important;
|
||
background: white !important;
|
||
box-shadow: none !important;
|
||
text-shadow: none !important;
|
||
}
|
||
|
||
.print-table th,
|
||
.print-table td {
|
||
border: 2px solid black !important;
|
||
background: white !important;
|
||
vertical-align: top;
|
||
}
|
||
|
||
.print-table th {
|
||
padding: 8px 12px !important;
|
||
}
|
||
|
||
.print-table thead tr {
|
||
background: #f5f5f5 !important;
|
||
}
|
||
}
|
||
`}</style>
|
||
|
||
<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>
|
||
|
||
{/* Print content */}
|
||
<div className="p-8">
|
||
<table className="w-full border-collapse border-2 border-gray-800 print-table">
|
||
<thead>
|
||
<tr className="bg-gray-100">
|
||
<th className="border-2 border-gray-800 px-3 py-2 text-center font-bold text-sm w-16">
|
||
{translations.tableHeaderIndex}
|
||
</th>
|
||
<th className="border-2 border-gray-800 px-3 py-2 text-left font-bold text-sm">
|
||
{translations.tableHeaderBillInfo}
|
||
</th>
|
||
<th className="border-2 border-gray-800 px-3 py-2 text-center font-bold text-sm w-64">
|
||
{translations.tableHeaderBarcode}
|
||
</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{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-medium text-sm text-gray-800">
|
||
🏠 {item.locationName}
|
||
</div>
|
||
<div className="text-sm text-gray-700">
|
||
📋 {item.billName}
|
||
</div>
|
||
{item.payedAmount && (
|
||
<div className="text-sm text-gray-700">
|
||
💰 {(item.payedAmount / 100).toFixed(2)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</td>
|
||
<td className="border-2 border-gray-800 px-3 py-1.5 text-center">
|
||
<div className="flex justify-center items-center">
|
||
{
|
||
item.hub3aText ? <Pdf417Barcode hub3aText={item.hub3aText} className="print:m-[5em_auto]" /> : null
|
||
}
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
|
||
{/* Print footer - only visible when printing */}
|
||
<div className="mt-6 text-center text-xs text-gray-500 hidden print:block">
|
||
<p>{translations.printFooter}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</>
|
||
);
|
||
}; |