PDF Export Design an neues Design angepasst und PDF größe verringert
This commit is contained in:
1 parent
004003cd17
commit
c055091dc7
12 files changed
+93
-43
No files matched your search
+89
-39
@@ -2246,9 +2246,9 @@ async function exportPDF(){
|
||||
container.style.width = '1200px';
|
||||
container.style.background = '#fff';
|
||||
container.style.padding = '20px';
|
||||
container.style.fontFamily = 'Arial, sans-serif';
|
||||
container.style.fontFamily = '-apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
||||
container.style.fontSize = '10px';
|
||||
container.style.color = '#1a1a1a';
|
||||
container.style.color = 'var(--text)';
|
||||
document.body.appendChild(container);
|
||||
|
||||
const grouped = peopleByRole(w);
|
||||
@@ -2283,16 +2283,16 @@ async function exportPDF(){
|
||||
cover.style.flexDirection = 'column';
|
||||
cover.style.justifyContent = 'center';
|
||||
cover.style.textAlign = 'center';
|
||||
cover.style.fontFamily = 'Arial, sans-serif';
|
||||
cover.style.fontFamily = '-apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
||||
const logoHtml = logoDataUrl
|
||||
? `<div style="margin-bottom:28px;"><img src="${logoDataUrl}" alt="Logo" style="width:200px; height:200px; object-fit:contain;"></div>`
|
||||
: `<div style="font-size:80px; margin-bottom:28px;">⚡</div>`;
|
||||
cover.innerHTML = `
|
||||
${logoHtml}
|
||||
<h1 style="font-size:32px; margin:0 0 16px 0; color:#1e3a5f;">Projektübersicht ${DEPARTMENTS[currentDepartment].label}</h1>
|
||||
<div style="font-size:18px; color:#4a5568; margin-bottom:48px;">${esc(w.label)}</div>
|
||||
${w.date ? `<div style="font-size:14px; color:#718096; margin-bottom:12px;">Datum: ${esc(formatWeekDate(w.date))}</div>` : ''}
|
||||
<div style="font-size:12px; color:#a0aec0; margin-top:60px;">Erstellt am ${new Date().toLocaleDateString('de-DE')}</div>
|
||||
<h1 style="font-size:32px; margin:0 0 16px 0; color:#1d1d1f;">Projektübersicht ${DEPARTMENTS[currentDepartment].label}</h1>
|
||||
<div style="font-size:18px; color:#6e6e73; margin-bottom:48px;">${esc(w.label)}</div>
|
||||
${w.date ? `<div style="font-size:14px; color:#6e6e73; margin-bottom:12px;">Datum: ${esc(formatWeekDate(w.date))}</div>` : ''}
|
||||
<div style="font-size:12px; color:#6e6e73; margin-top:60px;">Erstellt am ${new Date().toLocaleDateString('de-DE')}</div>
|
||||
`;
|
||||
container.appendChild(cover);
|
||||
|
||||
@@ -2313,11 +2313,14 @@ async function exportPDF(){
|
||||
const landscapePageW = pageHeightPx - marginPx * 2;
|
||||
const landscapePageH = pageWidthPx - marginPx * 2;
|
||||
|
||||
// JPEG quality (0.1 - 1.0) - lower = smaller file
|
||||
const JPEG_QUALITY = 1;
|
||||
|
||||
async function addImageToLandscapePage(canvas){
|
||||
const imgW = landscapePageW;
|
||||
const imgH = (canvas.height * imgW) / canvas.width;
|
||||
pdf.addPage([pageHeightPx, pageWidthPx], 'landscape');
|
||||
pdf.addImage(canvas.toDataURL('image/png'), 'PNG', marginPx, marginPx, imgW, imgH);
|
||||
pdf.addImage(canvas.toDataURL('image/jpeg', JPEG_QUALITY), 'JPEG', marginPx, marginPx, imgW, imgH);
|
||||
}
|
||||
|
||||
async function drawBlockOnLandscapePage(blockHTML){
|
||||
@@ -2328,7 +2331,7 @@ async function exportPDF(){
|
||||
wrapper.style.background = '#fff';
|
||||
wrapper.innerHTML = blockHTML;
|
||||
container.appendChild(wrapper);
|
||||
const canvas = await html2canvas(wrapper, { scale: 2, useCORS: true, backgroundColor: '#ffffff', width: 1123 });
|
||||
const canvas = await html2canvas(wrapper, { scale: 1.5, useCORS: true, backgroundColor: '#ffffff', width: 1123 });
|
||||
const imgH = (canvas.height * landscapePageW) / canvas.width;
|
||||
wrapper.remove();
|
||||
return { canvas, imgH };
|
||||
@@ -2336,12 +2339,12 @@ async function exportPDF(){
|
||||
|
||||
try {
|
||||
// Deckblatt (Hochkant) zeichnen
|
||||
const coverCanvas = await html2canvas(cover, { scale: 2, useCORS: true, backgroundColor: '#ffffff', width: 595, height: 842 });
|
||||
const coverCanvas = await html2canvas(cover, { scale: 1.5, useCORS: true, backgroundColor: '#ffffff', width: 595, height: 842 });
|
||||
if(pdf.internal.getNumberOfPages() > 0) pdf.addPage();
|
||||
const coverW = pageWidthPx - marginPx * 2;
|
||||
const coverH = (coverCanvas.height * coverW) / coverCanvas.width;
|
||||
const coverX = marginPx + (coverW - (coverCanvas.width * coverW / coverCanvas.width)) / 2;
|
||||
pdf.addImage(coverCanvas.toDataURL('image/png'), 'PNG', marginPx, marginPx + ((pageHeightPx - marginPx * 2 - coverH) / 2), coverW, coverH);
|
||||
pdf.addImage(coverCanvas.toDataURL('image/jpeg', JPEG_QUALITY), 'JPEG', marginPx, marginPx + ((pageHeightPx - marginPx * 2 - coverH) / 2), coverW, coverH);
|
||||
|
||||
const headerRows = buildPdfTableHeaderRows(allPeople);
|
||||
|
||||
@@ -2356,7 +2359,7 @@ async function exportPDF(){
|
||||
// Hilfsfunktion: misst Höhe einer Tabelle mit gegebenen Zeilen
|
||||
async function measureBlockWithRows(titleRow, bodyRows){
|
||||
const wrapper = renderSinglePdfBlock(container, headerRows, titleRow, bodyRows);
|
||||
const canvas = await html2canvas(wrapper, { scale: 2, useCORS: true, backgroundColor: '#ffffff', width: 1123 });
|
||||
const canvas = await html2canvas(wrapper, { scale: 1.5, useCORS: true, backgroundColor: '#ffffff', width: 1123 });
|
||||
const imgH = (canvas.height * landscapePageW) / canvas.width;
|
||||
wrapper.remove();
|
||||
return { canvas, imgH };
|
||||
@@ -2375,7 +2378,10 @@ async function exportPDF(){
|
||||
|
||||
for(let i = 0; i < projects.length; i++){
|
||||
const rowHTML = buildPdfProjectRow(projects[i], allPeople);
|
||||
const testRows = currentRows + rowHTML;
|
||||
// Alternating row background to match project board design
|
||||
const rowBg = i % 2 === 0 ? ' style="background:#fafafc"' : '';
|
||||
const rowHTMLWithBg = rowHTML.replace('<tr>', `<tr${rowBg}>`);
|
||||
const testRows = currentRows + rowHTMLWithBg;
|
||||
const { imgH: testImgH } = await measureBlockWithRows(titleRow, testRows);
|
||||
|
||||
// Wenn die aktuelle Seite überschritten würde und wir bereits Zeilen haben,
|
||||
@@ -2396,7 +2402,7 @@ async function exportPDF(){
|
||||
// Projektboard-Blöcke zeichnen
|
||||
for(const block of groupBlocks){
|
||||
const wrapper = renderSinglePdfBlock(container, headerRows, block.titleRow, block.bodyRows);
|
||||
const canvas = await html2canvas(wrapper, { scale: 2, useCORS: true, backgroundColor: '#ffffff', width: 1123 });
|
||||
const canvas = await html2canvas(wrapper, { scale: 1.5, useCORS: true, backgroundColor: '#ffffff', width: 1123 });
|
||||
await addImageToLandscapePage(canvas);
|
||||
wrapper.remove();
|
||||
}
|
||||
@@ -2424,25 +2430,36 @@ async function exportPDF(){
|
||||
|
||||
// buildPdfTableHeaderRows: baut die Kopfzeilen der Projektübersichts-Tabelle als HTML-String (zwei Zeilen).
|
||||
function buildPdfTableHeaderRows(allPeople){
|
||||
let h1 = `<tr><td rowspan="2" style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Pr.-Nr.</td><td rowspan="2" style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Bezeichnung</td><td rowspan="2" style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Status</td>`;
|
||||
let h1 = `<tr><td rowspan="2" style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Pr.-Nr.</td><td rowspan="2" style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Bezeichnung</td><td rowspan="2" style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Status</td>`;
|
||||
const grouped = peopleByRole(currentWeek());
|
||||
ROLES.forEach(r => {
|
||||
const count = Math.max(1, grouped[r].length);
|
||||
h1 += `<td class="role-header role-${r.toLowerCase()}" colspan="${count}" style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">${r}</td>`;
|
||||
// Rollen-spezifische Hintergrundfarben aus dem Projektboard
|
||||
let roleBg;
|
||||
if(r === 'Planung') roleBg = 'rgb(212, 245, 224)';
|
||||
else if(r === 'Projektleitung') roleBg = 'rgb(255, 219, 219)';
|
||||
else if(r === 'Bauüberwachung') roleBg = 'rgb(203, 238, 255)';
|
||||
else roleBg = '#f0f0f2';
|
||||
h1 += `<td class="role-header role-${r.toLowerCase()}" colspan="${count}" style="border:1px solid #e8e8ed;background:${roleBg};color:#1d1d1f;padding:6px;font-weight:bold;">${r}</td>`;
|
||||
});
|
||||
h1 += `<td colspan="${LPHS.length}" style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Leistungsphasen (%)</td></tr>`;
|
||||
h1 += `<td colspan="${LPHS.length}" style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Leistungsphasen (%)</td></tr>`;
|
||||
|
||||
let h2 = `<tr>`;
|
||||
ROLES.forEach(r => {
|
||||
let roleBg;
|
||||
if(r === 'Planung') roleBg = 'rgb(212, 245, 224)';
|
||||
else if(r === 'Projektleitung') roleBg = 'rgb(255, 219, 219)';
|
||||
else if(r === 'Bauüberwachung') roleBg = 'rgb(203, 238, 255)';
|
||||
else roleBg = '#fafafc';
|
||||
if(grouped[r].length === 0){
|
||||
h2 += `<td style="border:1px solid #4a5568;background:#cbd5e0;padding:4px;font-weight:bold;">–</td>`;
|
||||
h2 += `<td style="border:1px solid #e8e8ed;background:${roleBg};padding:4px;font-weight:bold;">–</td>`;
|
||||
} else {
|
||||
grouped[r].forEach(entry => {
|
||||
h2 += `<td style="border:1px solid #4a5568;background:#cbd5e0;padding:4px;font-weight:bold;">${esc(entry.person.name)}</td>`;
|
||||
h2 += `<td style="border:1px solid #e8e8ed;background:${roleBg};padding:4px;font-weight:bold;">${esc(entry.person.name)}</td>`;
|
||||
});
|
||||
}
|
||||
});
|
||||
LPHS.forEach(l => h2 += `<td style="border:1px solid #4a5568;background:#cbd5e0;padding:4px;font-weight:bold;">${l}</td>`);
|
||||
LPHS.forEach(l => h2 += `<td style="border:1px solid #e8e8ed;background:#fafafc;padding:4px;font-weight:bold;">${l}</td>`);
|
||||
h2 += `</tr>`;
|
||||
return h1 + h2;
|
||||
}
|
||||
@@ -2450,28 +2467,61 @@ function buildPdfTableHeaderRows(allPeople){
|
||||
// buildPdfProjectRow: baut eine einzelne Projektzeile als HTML-String.
|
||||
function buildPdfProjectRow(p, allPeople){
|
||||
let body = `<tr>`;
|
||||
body += `<td style="border:1px solid #a0aec0;padding:4px;white-space:nowrap;">${esc(p.nr)}</td>`;
|
||||
body += `<td style="border:1px solid #a0aec0;padding:4px;">${esc(p.name)}</td>`;
|
||||
body += `<td style="border:1px solid #e8e8ed;padding:4px;white-space:nowrap;">${esc(p.nr)}</td>`;
|
||||
body += `<td style="border:1px solid #e8e8ed;padding:4px;">${esc(p.name)}</td>`;
|
||||
const statusText = pdfStatusText(p.status);
|
||||
body += `<td style="border:1px solid #a0aec0;padding:4px;">${statusText}</td>`;
|
||||
body += `<td style="border:1px solid #e8e8ed;padding:4px;">${statusText}</td>`;
|
||||
allPeople.forEach(entry => {
|
||||
const s = p.assign[assignKey(entry.person.id, entry.role)] || 0;
|
||||
const txt = s === 1 ? 'X' : s === 2 ? '(X)' : '';
|
||||
const bg = s === 1 ? '#9ae6b4' : s === 2 ? '#fbd38d' : '#fff';
|
||||
body += `<td style="border:1px solid #a0aec0;padding:4px;text-align:center;background:${bg};">${txt}</td>`;
|
||||
let roleBg = '#fff';
|
||||
if(entry.role === 'Planung') roleBg = 'rgb(212, 245, 224)';
|
||||
else if(entry.role === 'Projektleitung') roleBg = 'rgb(255, 219, 219)';
|
||||
else if(entry.role === 'Bauüberwachung') roleBg = 'rgb(203, 238, 255)';
|
||||
const textColor = (s === 1 || s === 2) ? '#fff' : '#1d1d1f';
|
||||
const cellBg = s === 1 ? '#248a3d' : s === 2 ? '#d18b00' : roleBg;
|
||||
body += `<td style="border:1px solid #e8e8ed;padding:4px;text-align:center;background:${cellBg};color:${textColor};">${txt}</td>`;
|
||||
});
|
||||
LPHS.forEach(l => {
|
||||
const v = (p.lph[l] !== undefined && p.lph[l] !== '') ? String(p.lph[l]).replace('%','') : '';
|
||||
body += `<td style="border:1px solid #a0aec0;padding:4px;text-align:center;">${esc(v ? v + '%' : '')}</td>`;
|
||||
body += `<td style="border:1px solid #e8e8ed;padding:4px;text-align:center;">${esc(v ? v + '%' : '')}</td>`;
|
||||
});
|
||||
body += `</tr>`;
|
||||
return body;
|
||||
}
|
||||
|
||||
// Build PDF group body rows with alternating background colors
|
||||
function buildPdfGroupBodyRows(projectRows, allPeople) {
|
||||
let html = '';
|
||||
projectRows.forEach((pr, i) => {
|
||||
const s = pr.assign ? Object.values(pr.assign)[0] || 0 : 0;
|
||||
const isEven = i % 2 === 0;
|
||||
const rowBg = isEven ? '#fafafc' : '#fff';
|
||||
html += `<tr style="background:${rowBg}">`;
|
||||
html += `<td style="border:1px solid #e8e8ed;padding:4px;white-space:nowrap;">${esc(pr.nr)}</td>`;
|
||||
html += `<td style="border:1px solid #e8e8ed;padding:4px;">${esc(pr.name)}</td>`;
|
||||
const statusText = pdfStatusText(pr.status);
|
||||
html += `<td style="border:1px solid #e8e8ed;padding:4px;">${statusText}</td>`;
|
||||
allPeople.forEach(entry => {
|
||||
const assignKey = `${entry.person.id}-${entry.role}`;
|
||||
const assign = pr.assign ? pr.assign[assignKey] : 0;
|
||||
const txt = assign === 1 ? 'X' : assign === 2 ? '(X)' : '';
|
||||
const bg = assign === 1 ? '#248a3d' : assign === 2 ? '#d18b00' : '#fff';
|
||||
html += `<td style="border:1px solid #e8e8ed;padding:4px;text-align:center;background:${bg};color:${assign===1||assign===2?'#fff':'#1d1d1f'}";">${txt}</td>`;
|
||||
});
|
||||
LPHS.forEach(l => {
|
||||
const v = (pr.lph && pr.lph[l] !== undefined && pr.lph[l] !== '') ? String(pr.lph[l]).replace('%','') : '';
|
||||
html += `<td style="border:1px solid #e8e8ed;padding:4px;text-align:center;">${esc(v ? v + '%' : '')}</td>`;
|
||||
});
|
||||
html += `</tr>`;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
|
||||
// buildPdfGroupTitleRow: baut die Zeile mit dem Gruppentitel.
|
||||
function buildPdfGroupTitleRow(groupName, allPeople){
|
||||
const colCount = 3 + allPeople.length + LPHS.length;
|
||||
return `<tr><td colspan="${colCount}" style="border:1px solid #4a5568;background:#edf2f7;font-weight:bold;padding:6px;">${esc(groupName)}</td></tr>`;
|
||||
return `<tr><td colspan="${colCount}" style="border:1px solid #e8e8ed;background:#f0f0f2;font-weight:bold;padding:6px;color:#1d1d1f;">${esc(groupName)}</td></tr>`;
|
||||
}
|
||||
|
||||
// renderSinglePdfBlock: erzeugt einen DOM-Wrapper mit einer einzelnen Tabelle
|
||||
@@ -2490,7 +2540,7 @@ function renderSinglePdfBlock(container, headerRows, titleRow, bodyRows){
|
||||
// buildPdfAttendanceBlock: baut den Anwesenheits-Block als HTML-String.
|
||||
function buildPdfAttendanceBlock(presentList, absentList){
|
||||
return `<div class="pdf-block" style="margin-bottom:16px;">
|
||||
<div style="margin-bottom:8px; font-weight:bold; color:#1e3a5f; font-size:12px;">Anwesenheit</div>
|
||||
<div style="margin-bottom:8px; font-weight:bold; color:#1d1d1f; font-size:12px;">Anwesenheit</div>
|
||||
<div style="margin-bottom:4px;"><b>Anwesend:</b> ${presentList || '—'}</div>
|
||||
<div><b>Abwesend:</b> ${absentList || '—'}</div>
|
||||
</div>`;
|
||||
@@ -2545,33 +2595,33 @@ function buildPdfLoadSection(allPeople){
|
||||
if(l > maxLoad) maxLoad = l;
|
||||
});
|
||||
|
||||
let html = `<div class="pdf-block"><div style="margin-top:24px; margin-bottom:8px; font-weight:bold; color:#1e3a5f; font-size:12px;">Auslastung der Projektbeteiligten (aktuelle Sitzung)</div>`;
|
||||
html += '<table style="border-collapse:collapse;width:100%;"><tbody><tr><td style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Rolle</td><td style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Kürzel</td><td style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Punkte</td><td style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Balken</td><td style="border:1px solid #4a5568;background:#1e3a5f;color:#fff;padding:6px;font-weight:bold;">Projektdetails</td></tr>';
|
||||
let html = `<div class="pdf-block"><div style="margin-top:24px; margin-bottom:8px; font-weight:bold; color:#1d1d1f; font-size:12px;">Auslastung der Projektbeteiligten (aktuelle Sitzung)</div>`;
|
||||
html += '<table style="border-collapse:collapse;width:100%;"><tbody><tr><td style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Rolle</td><td style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Kürzel</td><td style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Punkte</td><td style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Balken</td><td style="border:1px solid #e8e8ed;background:#f0f0f2;color:#1d1d1f;padding:6px;font-weight:bold;">Projektdetails</td></tr>';
|
||||
|
||||
ROLES.forEach(r => {
|
||||
grouped[r].forEach(entry => {
|
||||
const s = stats[assignKey(entry.person.id, r)];
|
||||
const load = s.main + s.opt * 0.5 + s.kleine * 0.3;
|
||||
const pct = Math.round(load / maxLoad * 100);
|
||||
const color = pct > 75 ? '#c53030' : pct > 45 ? '#d69e2e' : '#2f855a';
|
||||
const color = pct > 75 ? '#d70015' : pct > 45 ? '#b25000' : '#248a3d';
|
||||
const detail = Object.keys(s.projects).map(k => `${esc(k)} (${s.projects[k]}×)`).join(', ') || '—';
|
||||
html += `<tr>
|
||||
<td style="border:1px solid #a0aec0;padding:4px;">${r}</td>
|
||||
<td style="border:1px solid #a0aec0;padding:4px;font-weight:bold;">${esc(entry.person.name)}</td>
|
||||
<td style="border:1px solid #a0aec0;padding:4px;text-align:center;">${load.toString().replace('.',',')}</td>
|
||||
<td style="border:1px solid #a0aec0;padding:4px;">
|
||||
<div style="background:#e2e8f0;height:10px;border-radius:3px;overflow:hidden;">
|
||||
<td style="border:1px solid #e8e8ed;padding:4px;">${r}</td>
|
||||
<td style="border:1px solid #e8e8ed;padding:4px;font-weight:bold;">${esc(entry.person.name)}</td>
|
||||
<td style="border:1px solid #e8e8ed;padding:4px;text-align:center;">${load.toString().replace('.',',')}</td>
|
||||
<td style="border:1px solid #e8e8ed;padding:4px;">
|
||||
<div style="background:#f0f0f0;height:10px;border-radius:3px;overflow:hidden;">
|
||||
<div style="width:${pct}%;background:${color};height:100%;"></div>
|
||||
</div>
|
||||
<div style="font-size:9px;color:#666;text-align:right;">${pct}%</div>
|
||||
<div style="font-size:9px;color:#6e6e73;text-align:right;">${pct}%</div>
|
||||
</td>
|
||||
<td style="border:1px solid #a0aec0;padding:4px;font-size:9px;">${detail}${s.anbahnung ? `<br><span style="color:#3182ce;">In Anbahnung: ${s.anbahnung}</span>` : ''}</td>
|
||||
<td style="border:1px solid #e8e8ed;padding:4px;font-size:9px;">${detail}${s.anbahnung ? `<br><span style="color:#0071e3;">In Anbahnung: ${s.anbahnung}</span>` : ''}</td>
|
||||
</tr>`;
|
||||
});
|
||||
});
|
||||
|
||||
html += '</tbody></table>';
|
||||
html += '<div style="margin-top:4px;font-size:9px;color:#666;">Haupt=1 · Optional=0,5 · Kleine Projekte=0,3 · In Anbahnung wird nicht in der Punktzahl gezählt.</div>';
|
||||
html += '<div style="margin-top:4px;font-size:9px;color:#6e6e73;">Haupt=1 · Optional=0,5 · Kleine Projekte=0,3 · In Anbahnung wird nicht in der Punktzahl gezählt.</div>';
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user