650 lines
25 KiB
JavaScript
650 lines
25 KiB
JavaScript
import {
|
||
uniqueConstructeurIds,
|
||
resolveConstructeurs,
|
||
} from '~/shared/constructeurUtils'
|
||
|
||
const formatSize = (size) => {
|
||
if (size === undefined || size === null) { return '—' }
|
||
if (size === 0) { return '0 B' }
|
||
const units = ['B', 'KB', 'MB', 'GB']
|
||
const index = Math.min(units.length - 1, Math.floor(Math.log(size) / Math.log(1024)))
|
||
const formatted = size / Math.pow(1024, index)
|
||
return `${formatted.toFixed(1)} ${units[index]}`
|
||
}
|
||
|
||
const renderPrintField = (label, value, fallback = '—') => {
|
||
const display = value !== undefined && value !== null && value !== '' ? value : fallback
|
||
return `<div class="print-field"><label>${label}</label><span>${display}</span></div>`
|
||
}
|
||
|
||
const renderPrintCustomFields = (fields = [], title, sectionClass = 'print-section') => {
|
||
if (!fields.length) { return '' }
|
||
const items = fields
|
||
.map(field => `<div class="print-field"><label>${field.label}</label><span>${field.value || '—'}</span></div>`)
|
||
.join('')
|
||
return `
|
||
<div class="${sectionClass}">
|
||
<h3>${title}</h3>
|
||
<div class="print-grid">
|
||
${items}
|
||
</div>
|
||
</div>
|
||
`
|
||
}
|
||
|
||
const renderPrintDocuments = (documents = [], title, sectionClass = 'print-section') => {
|
||
if (!documents.length) { return '' }
|
||
const rows = documents
|
||
.map(doc => `<tr><td>${doc.name}</td><td>${doc.type}</td><td>${doc.size}</td></tr>`)
|
||
.join('')
|
||
return `
|
||
<div class="${sectionClass}">
|
||
<h3>${title}</h3>
|
||
<table class="print-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Nom</th>
|
||
<th>Type</th>
|
||
<th>Taille</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>${rows}</tbody>
|
||
</table>
|
||
</div>
|
||
`
|
||
}
|
||
|
||
const renderPrintPieces = (
|
||
pieces = [],
|
||
title = 'Pièces indépendantes',
|
||
sectionClass = 'print-section print-section--pieces'
|
||
) => {
|
||
if (!pieces.length) { return '' }
|
||
|
||
const cards = pieces
|
||
.map((piece, idx) => {
|
||
const indexLabel = piece.indexPath ? piece.indexPath.join('.') : `${idx + 1}`
|
||
const constructeurBadges = (piece.constructeurs || [])
|
||
.map((constructeur, badgeIdx) => {
|
||
const suffix = piece.constructeurs.length > 1 ? ` ${badgeIdx + 1}` : ''
|
||
return `<span class="print-badge print-badge--subtle">Constructeur${suffix}: ${constructeur.name}</span>`
|
||
})
|
||
.join('')
|
||
|
||
const customFields = (piece.customFields || [])
|
||
.filter(field => field.value && field.value !== '—' && field.value !== '')
|
||
.map(
|
||
field => `
|
||
<li>
|
||
<span class="print-list-label">${field.label}</span>
|
||
<span class="print-list-value">${field.value}</span>
|
||
</li>
|
||
`
|
||
)
|
||
.join('')
|
||
|
||
const customFieldsBlock = customFields
|
||
? `<div class="print-piece-section"><h4>Champs personnalisés</h4><ul class="print-list">${customFields}</ul></div>`
|
||
: ''
|
||
|
||
const documentsBlock = (piece.documents || []).length
|
||
? `<div class="print-piece-section"><h4>Documents</h4><ul class="print-list">${piece.documents
|
||
.map(doc => `<li>${doc.name} <span class="print-list-hint">(${doc.type} • ${doc.size})</span></li>`)
|
||
.join('')}</ul></div>`
|
||
: ''
|
||
|
||
return `
|
||
<div class="print-piece-card">
|
||
<div class="print-piece-header">
|
||
<span class="print-index print-index--piece">${indexLabel}</span>
|
||
<div class="print-piece-heading">
|
||
<div class="print-piece-title">${piece.name}</div>
|
||
<div class="print-piece-subtitle">${piece.reference || 'Référence non définie'}</div>
|
||
</div>
|
||
${constructeurBadges}
|
||
</div>
|
||
${piece.description ? `<p class="print-piece-description">${piece.description}</p>` : ''}
|
||
<div class="print-piece-meta">
|
||
<div class="print-field-mini">
|
||
<label>Constructeur(s)</label>
|
||
<span>${piece.constructeurs?.length
|
||
? piece.constructeurs.map(constructeur => constructeur.name).join(', ')
|
||
: '—'}</span>
|
||
</div>
|
||
<div class="print-field-mini">
|
||
<label>Contact(s)</label>
|
||
<span>${piece.constructeurs?.length
|
||
? piece.constructeurs
|
||
.map(constructeur => constructeur.contact)
|
||
.filter(Boolean)
|
||
.join(' • ') || '—'
|
||
: '—'}</span>
|
||
</div>
|
||
</div>
|
||
${customFieldsBlock}
|
||
${documentsBlock}
|
||
</div>
|
||
`
|
||
})
|
||
.join('')
|
||
|
||
return `
|
||
<div class="${sectionClass}">
|
||
<h3>${title}</h3>
|
||
<div class="print-piece-grid">
|
||
${cards}
|
||
</div>
|
||
</div>
|
||
`
|
||
}
|
||
|
||
const renderPrintComponents = (components = [], depth = 0, indexPath = []) => {
|
||
if (!components.length) { return '' }
|
||
return components
|
||
.map((component, idx) => {
|
||
const badges = []
|
||
if (component.constructeurs?.length) {
|
||
const label = component.constructeurs.map((constructeur, badgeIdx) => {
|
||
const suffix = component.constructeurs.length > 1 ? ` ${badgeIdx + 1}` : ''
|
||
return `Constructeur${suffix}: ${constructeur.name}`
|
||
})
|
||
badges.push(...label)
|
||
}
|
||
const sectionClass = `print-section print-section--component print-section-depth-${Math.min(depth, 3)}`
|
||
const currentIndex = [...indexPath, idx + 1]
|
||
const indexLabel = currentIndex.join('.')
|
||
return `
|
||
<div class="${sectionClass}">
|
||
<h3>
|
||
<span class="print-index print-index--component">${indexLabel}</span>
|
||
<span>Composant : ${component.name}</span>
|
||
</h3>
|
||
${component.description ? `<p class="print-muted">${component.description}</p>` : ''}
|
||
${badges.length ? `<div class="badge-group">${badges.map(badge => `<span class="print-badge">${badge}</span>`).join('')}</div>` : ''}
|
||
${renderPrintCustomFields(
|
||
component.customFields,
|
||
'Champs personnalisés',
|
||
'print-section print-subsection print-section--custom-fields'
|
||
)}
|
||
${renderPrintPieces(
|
||
(component.pieces || []).map((piece, pieceIdx) => ({ ...piece, indexPath: [...currentIndex, pieceIdx + 1] })),
|
||
'Pièces du composant',
|
||
'print-section print-subsection print-section--pieces'
|
||
)}
|
||
${renderPrintDocuments(
|
||
component.documents,
|
||
'Documents du composant',
|
||
'print-section print-subsection print-section--documents'
|
||
)}
|
||
${renderPrintComponents(component.subComponents || [], depth + 1, currentIndex)}
|
||
</div>
|
||
`
|
||
})
|
||
.join('')
|
||
}
|
||
|
||
const normalizeDocuments = (docs = []) => {
|
||
return docs.map(doc => ({
|
||
id: doc.id,
|
||
name: doc.name || doc.filename || 'Document',
|
||
type: doc.mimeType || doc.type || '—',
|
||
size: formatSize(doc.size)
|
||
}))
|
||
}
|
||
|
||
const normalizeCustomFields = (values = []) => {
|
||
return values.map(value => ({
|
||
id: value.id,
|
||
label: value.customField?.name || 'Champ',
|
||
value: value.value || '—'
|
||
}))
|
||
}
|
||
|
||
const normalizeConstructeur = (constructeur) => {
|
||
if (!constructeur) { return null }
|
||
return {
|
||
id: constructeur.id || null,
|
||
name: constructeur.name || '—',
|
||
contact: [constructeur.email, constructeur.phone].filter(Boolean).join(' • ') || '—'
|
||
}
|
||
}
|
||
|
||
const normalizeConstructeurList = (...sources) => {
|
||
const ids = uniqueConstructeurIds(...sources)
|
||
const pools = sources
|
||
.flatMap((source) => {
|
||
if (Array.isArray(source)) {
|
||
if (source.length && typeof source[0] === 'object') {
|
||
return [source]
|
||
}
|
||
return []
|
||
}
|
||
if (source && typeof source === 'object' && 'id' in source) {
|
||
return [[source]]
|
||
}
|
||
return []
|
||
})
|
||
.filter(Boolean)
|
||
const resolved = resolveConstructeurs(ids, ...pools)
|
||
return resolved
|
||
.map(normalizeConstructeur)
|
||
.filter(Boolean)
|
||
}
|
||
|
||
const normalizePiece = piece => {
|
||
const constructeurs = normalizeConstructeurList(
|
||
piece.constructeurs,
|
||
piece.constructeur,
|
||
piece.originalPiece?.constructeurs,
|
||
piece.originalPiece?.constructeur,
|
||
piece.constructeurIds,
|
||
piece.constructeurId,
|
||
)
|
||
|
||
return {
|
||
id: piece.id,
|
||
name: piece.name || 'Pièce sans nom',
|
||
description: piece.description || '',
|
||
reference: piece.reference || '',
|
||
customFields: normalizeCustomFields(piece.customFieldValues || []),
|
||
documents: normalizeDocuments(piece.documents || []),
|
||
constructeurs,
|
||
constructeur: constructeurs[0] || null,
|
||
indexPath: piece.indexPath || null
|
||
}
|
||
}
|
||
|
||
const normalizeComponent = component => {
|
||
const constructeurs = normalizeConstructeurList(
|
||
component.constructeurs,
|
||
component.constructeur,
|
||
component.originalComposant?.constructeurs,
|
||
component.originalComposant?.constructeur,
|
||
component.constructeurIds,
|
||
component.constructeurId,
|
||
)
|
||
|
||
return {
|
||
id: component.id,
|
||
name: component.name || 'Composant sans nom',
|
||
description: component.description || '',
|
||
customFields: normalizeCustomFields(component.customFieldValues || []),
|
||
documents: normalizeDocuments(component.documents || []),
|
||
pieces: (component.pieces || []).map(normalizePiece),
|
||
subComponents: (component.sousComposants || component.subComponents || []).map(normalizeComponent),
|
||
constructeurs,
|
||
constructeur: constructeurs[0] || null,
|
||
}
|
||
}
|
||
|
||
export const buildMachinePrintContext = ({
|
||
machine,
|
||
machineName,
|
||
machineReference,
|
||
machinePieces = [],
|
||
components = [],
|
||
selection
|
||
}) => {
|
||
const selectionState = selection || {}
|
||
const machineSelection = selectionState.machine || {}
|
||
const componentSelection = selectionState.components || {}
|
||
const pieceSelection = selectionState.pieces || {}
|
||
|
||
const includeMachineInfo = machineSelection.info !== false
|
||
const includeMachineCustomFields = machineSelection.customFields !== false
|
||
const includeMachineDocuments = machineSelection.documents !== false
|
||
|
||
const isComponentSelected = (id) => {
|
||
if (!id) { return true }
|
||
if (Object.prototype.hasOwnProperty.call(componentSelection, id)) {
|
||
return componentSelection[id]
|
||
}
|
||
return true
|
||
}
|
||
|
||
const isPieceSelected = (id) => {
|
||
if (!id) { return true }
|
||
if (Object.prototype.hasOwnProperty.call(pieceSelection, id)) {
|
||
return pieceSelection[id]
|
||
}
|
||
return true
|
||
}
|
||
|
||
const machineBadges = []
|
||
if (machine?.typeMachine?.category) {
|
||
machineBadges.push(machine.typeMachine.category)
|
||
}
|
||
if (machine?.site?.name) {
|
||
machineBadges.push(`Site: ${machine.site.name}`)
|
||
}
|
||
if (machineReference) {
|
||
machineBadges.push(`Ref: ${machineReference}`)
|
||
}
|
||
|
||
const machineConstructeurs = normalizeConstructeurList(
|
||
machine?.constructeurs,
|
||
machine?.constructeur,
|
||
machine?.constructeurIds,
|
||
machine?.constructeurId,
|
||
)
|
||
|
||
const machineConstructeurNames = machineConstructeurs.length
|
||
? machineConstructeurs.map((constructeur) => constructeur.name).join(', ')
|
||
: ''
|
||
|
||
const machineConstructeurContacts = machineConstructeurs.length
|
||
? machineConstructeurs
|
||
.map((constructeur) => constructeur.contact)
|
||
.filter(Boolean)
|
||
.join(' • ')
|
||
: ''
|
||
|
||
const normalizedPieces = machinePieces
|
||
.map(normalizePiece)
|
||
.filter(piece => isPieceSelected(piece.id))
|
||
.map((piece, idx) => ({
|
||
...piece,
|
||
indexPath: [idx + 1]
|
||
}))
|
||
|
||
const normalizedComponents = components.map(normalizeComponent)
|
||
|
||
const filterComponentTree = (component) => {
|
||
const filteredPieces = (component.pieces || []).filter(piece => isPieceSelected(piece.id))
|
||
const filteredSubComponents = (component.subComponents || [])
|
||
.map(filterComponentTree)
|
||
.filter(Boolean)
|
||
|
||
const includeSelf = isComponentSelected(component.id)
|
||
const shouldInclude = includeSelf || filteredPieces.length > 0 || filteredSubComponents.length > 0
|
||
|
||
if (!shouldInclude) {
|
||
return null
|
||
}
|
||
|
||
return {
|
||
...component,
|
||
pieces: filteredPieces,
|
||
subComponents: filteredSubComponents
|
||
}
|
||
}
|
||
|
||
const filteredComponents = normalizedComponents
|
||
.map(filterComponentTree)
|
||
.filter(Boolean)
|
||
|
||
return {
|
||
generatedAt: new Date().toLocaleString('fr-FR'),
|
||
machine: {
|
||
id: machine?.id || null,
|
||
name: machineName,
|
||
description: machine?.description || '',
|
||
typeDescription: machine?.typeMachine?.description || '',
|
||
reference: machineReference,
|
||
site: machine?.site?.name || '',
|
||
category: machine?.typeMachine?.category || '',
|
||
badges: machineBadges,
|
||
constructeurs: machineConstructeurs,
|
||
constructeur: machineConstructeurs[0] || null,
|
||
constructeurNames: machineConstructeurNames,
|
||
constructeurContacts: machineConstructeurContacts,
|
||
includeInfo: includeMachineInfo,
|
||
customFields: includeMachineCustomFields
|
||
? normalizeCustomFields(machine?.customFieldValues || [])
|
||
: [],
|
||
documents: includeMachineDocuments
|
||
? normalizeDocuments(machine?.documents || [])
|
||
: []
|
||
},
|
||
components: filteredComponents,
|
||
pieces: normalizedPieces
|
||
}
|
||
}
|
||
|
||
export const buildMachinePrintHtml = (context, styles) => {
|
||
const title = context.machine.name ? `Impression - ${context.machine.name}` : 'Impression machine'
|
||
const badgesHtml = context.machine.badges
|
||
.map(badge => `<span class="print-badge">${badge}</span>`)
|
||
.join('')
|
||
const sections = []
|
||
|
||
sections.push(`
|
||
<div class="print-metadata">
|
||
<span>Généré le ${context.generatedAt}</span>
|
||
<span>Machine ID: ${context.machine.id || '—'}</span>
|
||
</div>
|
||
<div class="print-header">
|
||
<div class="title-block">
|
||
<div class="print-title">${context.machine.name || 'Machine sans nom'}</div>
|
||
<div class="print-subtitle">${
|
||
context.machine.description || context.machine.typeDescription || 'Aucune description disponible'
|
||
}</div>
|
||
</div>
|
||
<div class="badge-group">${badgesHtml}</div>
|
||
</div>
|
||
`)
|
||
|
||
if (context.machine.includeInfo) {
|
||
sections.push(`
|
||
<div class="print-section print-section--machine">
|
||
<h3>Informations générales</h3>
|
||
<div class="print-grid">
|
||
${renderPrintField('Nom', context.machine.name)}
|
||
${renderPrintField('Référence', context.machine.reference, 'Non définie')}
|
||
${renderPrintField('Site', context.machine.site, 'Non défini')}
|
||
${renderPrintField('Constructeur(s)', context.machine.constructeurNames, 'Non défini')}
|
||
${renderPrintField('Contact(s) Constructeur(s)', context.machine.constructeurContacts, 'Non défini')}
|
||
</div>
|
||
</div>
|
||
`)
|
||
}
|
||
|
||
const customFieldsSection = renderPrintCustomFields(
|
||
context.machine.customFields,
|
||
'Champs personnalisés de la machine',
|
||
'print-section print-section--custom-fields'
|
||
)
|
||
if (customFieldsSection) {
|
||
sections.push(customFieldsSection)
|
||
}
|
||
|
||
const documentsSection = renderPrintDocuments(
|
||
context.machine.documents,
|
||
'Documents liés à la machine',
|
||
'print-section print-section--documents'
|
||
)
|
||
if (documentsSection) {
|
||
sections.push(documentsSection)
|
||
}
|
||
|
||
const componentsSection = renderPrintComponents(context.components)
|
||
if (componentsSection) {
|
||
sections.push(componentsSection)
|
||
}
|
||
|
||
const piecesSection = renderPrintPieces(
|
||
context.pieces,
|
||
'Pièces indépendantes',
|
||
'print-section print-section--pieces'
|
||
)
|
||
if (piecesSection) {
|
||
sections.push(piecesSection)
|
||
}
|
||
|
||
sections.push(`
|
||
<div class="print-section print-muted">
|
||
Rapport généré automatiquement par Inventaire Pro.
|
||
</div>
|
||
`)
|
||
|
||
const content = sections.join('\n')
|
||
|
||
return `<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<meta name="color-scheme" content="light only" />
|
||
<title>${title}</title>
|
||
${styles}
|
||
<style>
|
||
/* ============ Design tokens (sobre & pro) ============ */
|
||
:root{
|
||
--bg: #ffffff;
|
||
--ink: #0f172a; /* slate-900 */
|
||
--muted: #475569; /* slate-600 */
|
||
--line: #cbd5e1; /* slate-300 */
|
||
--line-soft: #e2e8f0; /* slate-200 */
|
||
--accent: #1f2937; /* gray-800 */
|
||
--accent-ink: #111827; /* gray-900 */
|
||
--brand: #0ea5e9; /* sky-500 (léger) */
|
||
--brand-ink: #075985; /* sky-800 */
|
||
--ok: #059669; /* emerald-600 */
|
||
--warn: #ea580c; /* orange-600 */
|
||
--comp: #be185d; /* pink-700 */
|
||
--radius-xs: 2px;
|
||
--radius-sm: 4px;
|
||
--radius-md: 6px;
|
||
--radius-lg: 8px;
|
||
--shadow-0: none;
|
||
--shadow-1: 0 1px 0 rgba(15,23,42,.04);
|
||
--shadow-2: 0 2px 8px rgba(15,23,42,.06);
|
||
}
|
||
|
||
/* ============ Base ============ */
|
||
@page { margin: 16mm 14mm; }
|
||
html, body { height: 100%; }
|
||
body {
|
||
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Inter, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
|
||
background: #F9FAFB;
|
||
color: var(--ink);
|
||
-webkit-print-color-adjust: exact !important;
|
||
print-color-adjust: exact !important;
|
||
}
|
||
|
||
.print-layout { max-width: 1120px; margin: 0 auto; padding: 28px; display: flex; flex-direction: column; gap: 20px; }
|
||
|
||
.print-metadata {
|
||
font-size: 11px; color: var(--muted); display: flex; justify-content: space-between; align-items: center; gap: 12px;
|
||
letter-spacing: .04em; text-transform: uppercase; border-bottom: 1px solid var(--line); padding-bottom: 8px;
|
||
}
|
||
|
||
.print-header {
|
||
display: flex; justify-content: space-between; align-items: flex-start; gap: 24px;
|
||
background: var(--bg); padding: 18px 20px; border: 1px solid var(--line); border-radius: var(--radius-md);
|
||
box-shadow: var(--shadow-2);
|
||
}
|
||
.print-header .title-block { flex: 1; }
|
||
.print-title { font-size: 26px; font-weight: 800; margin: 0 0 6px; color: var(--accent-ink); }
|
||
.print-subtitle { font-size: 14px; color: var(--muted); margin: 0; line-height: 1.5; }
|
||
|
||
.badge-group { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
|
||
.print-badge {
|
||
display: inline-flex; align-items: center; padding: 4px 8px; border-radius: var(--radius-sm);
|
||
font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
|
||
background: #e0f2fe; color: var(--brand-ink); border: 1px solid #bae6fd;
|
||
}
|
||
.print-badge--subtle { background: #f1f5f9; border-color: var(--line); color: #334155; }
|
||
|
||
.print-index {
|
||
display: inline-flex; align-items: center; justify-content: center; min-width: 24px; height: 24px;
|
||
border-radius: var(--radius-sm); font-size: 12px; font-weight: 700; margin-right: 8px;
|
||
box-shadow: inset 0 0 0 1px var(--line);
|
||
}
|
||
.print-index--component { background: #fde2f2; color: var(--comp); }
|
||
.print-index--piece { background: #ffedd5; color: var(--warn); }
|
||
|
||
/* ============ Sections ============ */
|
||
.print-section, .print-section.print-subsection, .print-piece-card { break-inside: avoid; page-break-inside: avoid; }
|
||
.print-section {
|
||
margin: 0; padding: 20px 22px; border-radius: var(--radius-md);
|
||
border: 1px solid var(--line); background: var(--bg); box-shadow: var(--shadow-2); position: relative;
|
||
}
|
||
.print-section + .print-section { margin-top: 8px; }
|
||
.print-section h3 {
|
||
font-size: 16px; font-weight: 800; margin: 0 0 14px; letter-spacing: .02em; color: var(--accent);
|
||
border-left: 3px solid var(--accent); padding-left: 10px;
|
||
}
|
||
|
||
.print-section--machine { border-left: 3px solid var(--brand); }
|
||
.print-section--custom-fields { border-left: 3px solid #7c3aed; }
|
||
.print-section--documents { border-left: 3px solid var(--ok); }
|
||
.print-section--component { border-left: 3px solid var(--comp); }
|
||
.print-section--pieces { border-left: 3px solid var(--warn); }
|
||
|
||
.print-section.print-subsection {
|
||
margin-top: 14px; margin-bottom: 10px; padding: 16px 18px; border-radius: var(--radius-sm);
|
||
box-shadow: var(--shadow-1); background: #fcfcfd;
|
||
}
|
||
|
||
/* ============ Fields (grid) ============ */
|
||
.print-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; }
|
||
.print-field {
|
||
display: flex; flex-direction: column; padding: 10px 12px; border-radius: var(--radius-sm);
|
||
background: #fafbfc; border: 1px solid var(--line-soft); min-height: 72px;
|
||
}
|
||
.print-field label { font-size: 10px; text-transform: uppercase; color: #64748b; letter-spacing: .08em; margin-bottom: 8px; }
|
||
.print-field span { font-size: 14px; color: var(--ink); font-weight: 600; line-height: 1.45; }
|
||
|
||
/* ============ Pieces ============ */
|
||
.print-piece-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; }
|
||
.print-piece-card {
|
||
padding: 14px 16px; border-radius: var(--radius-md); border: 1px solid #fed7aa; background: var(--bg); box-shadow: var(--shadow-1);
|
||
display: flex; flex-direction: column; gap: 10px;
|
||
}
|
||
.print-piece-header { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||
.print-piece-heading { flex: 1 1 160px; }
|
||
.print-piece-title { font-size: 15px; font-weight: 800; color: #7c2d12; }
|
||
.print-piece-subtitle { font-size: 11px; color: #a16207; text-transform: uppercase; letter-spacing: .06em; margin-top: 2px; }
|
||
.print-piece-description { font-size: 12px; color: #7c2d12; margin: 0; }
|
||
|
||
.print-piece-meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 8px; }
|
||
.print-field-mini {
|
||
padding: 8px 10px; border-radius: var(--radius-sm); background: #fff7ed; border: 1px solid #fed7aa; display: flex; flex-direction: column; gap: 4px;
|
||
}
|
||
.print-field-mini label { font-size: 9px; text-transform: uppercase; color: #9a3412; letter-spacing: .06em; }
|
||
.print-field-mini span { font-size: 12px; font-weight: 700; color: #7c2d12; }
|
||
.print-piece-section { display: flex; flex-direction: column; gap: 6px; }
|
||
.print-piece-section h4 { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: #7c2d12; margin: 0; }
|
||
.print-list { margin: 0; padding-left: 16px; display: grid; gap: 4px; }
|
||
.print-list li { font-size: 12px; color: #475569; break-inside: avoid; }
|
||
.print-list-label { font-weight: 700; color: #7c2d12; margin-right: 4px; }
|
||
.print-list-value { color: var(--ink); }
|
||
.print-list-hint { color: #64748B; }
|
||
|
||
/* ============ Tables ============ */
|
||
.print-table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 13px; border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; }
|
||
.print-table th, .print-table td { border: 1px solid var(--line-soft); padding: 10px 12px; text-align: left; vertical-align: top; }
|
||
.print-table thead th { background: #f1f5f9; font-weight: 800; letter-spacing: .02em; text-transform: uppercase; color: var(--accent); font-size: 12px; }
|
||
.print-table tr { break-inside: avoid; page-break-inside: avoid; }
|
||
.print-section--documents .print-table thead th { background: #ecfdf5; color: #065f46; }
|
||
.print-section--pieces .print-table thead th { background: #fffbeb; color: #92400e; }
|
||
.print-section--component .print-table thead th { background: #fdf2f8; color: #831843; }
|
||
|
||
/* ============ Utilities ============ */
|
||
.print-muted { color: var(--muted); }
|
||
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); border: 0; }
|
||
|
||
/* ============ Print media ============ */
|
||
@media print {
|
||
body { background: white; }
|
||
.print-layout { padding: 0; max-width: none; }
|
||
.print-header, .print-section, .print-section.print-subsection, .print-piece-card, .print-table { box-shadow: var(--shadow-0); }
|
||
.print-section, .print-section.print-subsection, .print-piece-card { break-inside: avoid-page; page-break-inside: avoid; }
|
||
.badge-group { justify-content: flex-start; }
|
||
.print-metadata { border-bottom-color: var(--line); }
|
||
/* Force couleurs d’accent légères pour économie d’encre */
|
||
.print-badge { background: #e5f3fb !important; border-color: #d1e7f8 !important; }
|
||
.print-index--component { background: #fbe7f3 !important; }
|
||
.print-index--piece { background: #fff1e6 !important; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="print-layout">
|
||
${content}
|
||
</div>
|
||
</body>
|
||
</html>`
|
||
}
|