452 lines
10 KiB
Vue
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>
|