unilab-prints/src/App.vue
2025-07-21 17:27:51 +02:00

452 lines
10 KiB
Vue

<template>
<div>
<button class="pdf-button" @click="exportPdf(intestazione.nomefile ? `${intestazione.nomefile}.pdf` : 'certificato-prova.pdf')">Esporta PDF</button>
<div class="report" ref="printable" contenteditable="true">
<header>
<img src="/report_header.png" alt="Intestazione Unilab" class="report-header-image" />
</header>
<div class="header-row">
<h5 class="section-title">{{ intestazione.intest1 }}</h5>
<div class="modreport">{{ intestazione.modreport }}</div>
</div>
<h3 class="section-title">{{ intestazione.intest2 }}</h3>
<section class="info-block">
<table class="info-table">
<tr>
<td>Direttore dei Lavori:</td>
<td>{{ datiacc_info.directorName }}</td>
</tr>
<tr>
<td>Indirizzo:</td>
<td>{{ datiacc_info.directorAddress }}</td>
</tr>
<tr>
<td>Proprietà:</td>
<td>{{ datiacc_info.propertyName }}</td>
</tr>
<tr>
<td>Indirizzo:</td>
<td>{{ datiacc_info.propertyAddress }}</td>
</tr>
<tr>
<td>Cantiere:</td>
<td>{{ datiacc_info.siteName }}</td>
</tr>
<tr>
<td>Indirizzo:</td>
<td>{{ datiacc_info.siteAddress }}</td>
</tr>
<tr>
<td>Impresa esecutrice:</td>
<td>{{ datiacc_info.contractorName }}</td>
</tr>
<tr>
<td>Natura dei campioni:</td>
<td>{{ datiacc_info.sampleNature }}</td>
</tr>
</table>
</section>
<!-- SEZIONE DINAMICA PER MATERIALE E TIPO CAMPIONE-->
<div>
<component :is="resultComponent" :risult_data="risult_data" :intestazione="intestazione" :datiacc_data="datiacc_data" v-if="resultComponent" />
</div>
<!-- NOTE -->
<section v-if="note && note.length" class="note-section">
<table class="note-table">
<tr v-for="(n, index) in note" :key="index">
<td>{{ n.text }}</td>
</tr>
</table>
</section>
<footer>
<div class="notepp">{{ intestazione.notapp }}</div>
<div class="signatures">
<div class="signblock">
<span class="sign1">Lo sperimentatore</span><br>
<span class="sign2">Dott. Ing. Giacomo Calussi</span>
</div>
<div class="signblock">
<span class="sign1">Il Direttore del Laboratorio</span><br>
<span class="sign2">Dott. Ing. Paolo Neri</span>
</div>
</div>
</footer>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import html2pdf from 'html2pdf.js'
import A_CUB from './components/risultati/A_CUB.vue'
import A_CIL from './components/risultati/A_CIL.vue'
import B_BAR from './components/risultati/B_BAR.vue'
import B_RET from './components/risultati/B_RET.vue'
const componentMap = {
'A_CUB': A_CUB,
'A_CIL': A_CIL,
'B_BAR':B_BAR,
'B_RET':B_RET
}
const printable = ref(null)
const intestazione = ref({})
const datiacc_info = ref({})
const datiacc_data = ref([])
const risult_data = ref([])
const note = ref([])
const resultComponent = ref(null)
watch(
() => [intestazione.value.tipMat, intestazione.value.tipCam],
([tipMat, tipCam]) => {
const key = `${(tipMat || '').toUpperCase()}_${(tipCam || '').toUpperCase()}`
console.log('Updated key:', key)
resultComponent.value = componentMap[key] || null
},
{ immediate: true }
)
onMounted(async () => {
try {
const params = new URLSearchParams(window.location.search)
const pSERCER = params.get('pSERCER') ?? 'DEFAULT'
const token = await getLoginToken('servizio_api', 'p0l01nf.'); // credenziali da gestire lato sicurezza
const data = await fetchReportDataWithToken(token, pSERCER);
intestazione.value = data.intestazione
datiacc_info.value = data.datiacc_info
datiacc_data.value = data.datiacc_data
risult_data.value = calcolaMediaRcPerGruppo(data.risult_data)
note.value = data.note ?? []
console.log('intestazione', intestazione.value)
console.log('datiacc_info', datiacc_info.value)
console.log('datiacc_data', datiacc_data.value)
console.log('risult_data', risult_data.value)
} catch (err) {
console.error('Errore caricamento dati:', err)
}
})
async function getLoginToken(username, password) {
const credentials = btoa(`${username}:${password}`); // base64 encoding
const response = await fetch('/ahi/servlet/oauth/token?scope=logintoken', {
//const response = await fetch('/unilab/servlet/oauth/token?scope=logintoken', {
method: 'POST',
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json'
},
credentials: 'omit',
body: JSON.stringify({ "sp_company": "001" })
});
if (!response.ok) throw new Error('Autenticazione fallita');
const json = await response.json();
return json.access_token;
}
async function fetchReportDataWithToken(token, pSERCER) {
const response = await fetch(`/ahi/servlet/api/pi_flabreportapi?pSERCER=${encodeURIComponent(pSERCER)}`, {
//const response = await fetch(`/unilab/servlet/api/pi_flabreportapi?pSERCER=${encodeURIComponent(pSERCER)}`, {
headers: {
'Authorization': `Bearer ${token}`
},
credentials: 'omit'
});
if (!response.ok) throw new Error('Errore caricamento dati');
return await response.json();
}
function capitalizeAllFields(obj) {
const result = {}
for (const key in obj) {
const val = obj[key]
result[key] = typeof val === 'string' ? capitalizeEachWord(val) : val
}
return result
}
function capitalizeEachWord(str) {
if (!str) return ''
return str
.toLowerCase()
.split(' ')
.map(word =>
word.length > 0
? word[0].toUpperCase() + word.slice(1)
: ''
)
.join(' ')
}
function exportPdf(filename = 'certificato-prova.pdf') {
html2pdf()
.from(printable.value)
.set({
filename,
html2canvas: { scale: 2 },
jsPDF: {
unit: 'mm',
format: [210, 297], // A4
orientation: 'portrait'
}
})
.save()
}
function formatDate(dateStr) {
const date = new Date(dateStr)
return date.toLocaleDateString('it-IT')
}
function formatNumber(value, decimals = 2) {
return value != null
? Number(value).toLocaleString('it-IT', {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals
})
: '—'
}
function calcolaMediaRcPerGruppo(data) {
const gruppi = {}
// Raggruppa per serPre
data.forEach(row => {
if (!gruppi[row.serPre]) gruppi[row.serPre] = []
gruppi[row.serPre].push(row.rc)
})
// Calcola la media per gruppo
const medie = {}
for (const serPre in gruppi) {
const somma = gruppi[serPre].reduce((acc, val) => acc + val, 0)
const media = somma / gruppi[serPre].length
medie[serPre] = media
}
// Assegna la media a rprelievo per tutte le righe
return data.map(row => ({
...row,
rprelievo: formatNumber(medie[row.serPre],2)
}))
}
</script>
<style>
* {
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
padding: 2px;
}
.header-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px; /* opzionale, spazio sotto la riga */
}
.header-row .section-title {
margin: 0;
text-align: left;
}
.modreport {
font-weight: normal;
text-align: right;
margin: 0;
}
.report-header-image {
width: 100%;
height: auto;
margin-bottom: 8px;
display: block;
}
.report {
/* Assicuriamoci che .report abbia altezza A4 e posizionamento relativo */
position: relative;
max-width: 210mm;
min-height: 289mm;
padding: 4mm;
margin: 0 auto;
background: white;
box-sizing: border-box;
}
header {
margin-bottom: 4px;
}
.center {
text-align: center;
font-weight: bold;
margin: 4px 0;
}
.section-title {
padding: 4px;
font-weight: bold;
margin-top: 16px;
margin-bottom: 2px;
text-align: center;
white-space: pre-wrap; /* preserves newlines */
word-break: break-word; /* wraps long lines if needed */
}
.sub-header {
padding: 4px;
text-align: center;
}
table {
width: 100%;
border-collapse: collapse;
margin: 2px 0;
}
th,
td {
border: 1px solid #000;
padding: 1px;
font-size: 0.85em;
text-align: center;
}
.info-table {
width: 80%;
border: 2px solid black;
margin: 0 auto;
border-collapse: collapse;
}
.info-table th,
.info-table td {
border: 1px solid black;
padding: 2px;
text-align: left;
}
.info-table th:first-child,
.info-table td:first-child {
font-weight: bold;
width: 30%;
white-space: nowrap;
}
.note-section {
margin-top: 2px;
}
.note-table {
width: 100%;
border-collapse: collapse;
}
.note-table td {
padding: 1px 1px;
font-size: 0.7em;
border: none;
text-align: left;
}
.pdf-button {
position: fixed;
top: 20px;
right: 20px;
background-color: #007BFF;
color: white;
padding: 10px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
z-index: 999;
}
.pdf-button:hover {
background-color: #0056b3;
}
footer {
/* Posizione assoluta in fondo al contenitore .report */
position: absolute;
bottom: 4mm; /* distanza dal bordo inferiore */
left: 4mm; /* stesso padding orizzontale di .report */
right: 4mm;
}
footer .signatures {
/* Manteniamo le firme in orizzontale ma riduciamo margine sopra se serve */
margin-top: 0;
display: flex;
justify-content: space-between;
text-align: center;
padding-left: 80px;
padding-right: 80px;
border-top: 1px solid #000;
}
footer .sign1{
font-size: 0.75em;
}
footer .sign2{
font-size: 0.65em;
}
footer .signblock{
border-bottom: 1px solid #000;
padding-bottom: 12mm;
}
/* Stilizzo il div di notepp (assumo classe .modreport o ne crei una nuova) */
.modreport {
font-size: 0.7em; /* carattere piccolo */
margin: 0; /* rimuovo margini inutili */
text-align: right;
}
/* Se la sezione delle notepp ha una sua classe, ad esempio .notepp: */
.notepp {
font-size: 0.7em;
margin: 0;
}
</style>