feat: integrate Typst for PDF generation and add new fonts
- Added Typst templates for receipts and announcements. - Implemented TypstRunner and TypstRunner2 services for PDF generation. - Included necessary fonts for Typst rendering. - Updated Dockerfile to install Typst and Pandoc. - Enhanced API with new endpoints for Typst functionality. - Added temporary directory handling for generated files. - Updated .gitignore and .dockerignore to exclude temporary files.
This commit is contained in:
parent
aec6860cbd
commit
8c48dbb4b6
64 changed files with 524 additions and 1 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -29,3 +29,5 @@ apps/infoalloggi/fic_cities.json
|
|||
apps/infoalloggi/fic_nations.json
|
||||
apps/infoalloggi/gi_comuni_cap.json
|
||||
apps/infoalloggi/gi_nazioni.json
|
||||
apps/infoalloggi/tmp/
|
||||
|
||||
|
|
|
|||
|
|
@ -27,3 +27,4 @@ docs
|
|||
biome_plugins
|
||||
vitest.config.mts
|
||||
tsconfig.tsbuildinfo
|
||||
tmp
|
||||
|
|
|
|||
1
apps/infoalloggi/.vscode/settings.json
vendored
1
apps/infoalloggi/.vscode/settings.json
vendored
|
|
@ -21,4 +21,5 @@
|
|||
],
|
||||
"js/ts.tsdk.path": "node_modules\\typescript\\lib",
|
||||
"biome.lsp.trace.server": "verbose",
|
||||
"tinymist.fontPaths": ["${workspaceFolder}/typst/fonts"]
|
||||
}
|
||||
|
|
@ -88,7 +88,7 @@ ENV STORAGE_TOKEN=$STORAGE_TOKEN
|
|||
ARG BROWSER_WS_URL
|
||||
ENV BROWSER_WS_URL=$BROWSER_WS_URL
|
||||
|
||||
RUN apk add --no-cache curl
|
||||
RUN apk add --no-cache curl pandoc typst
|
||||
|
||||
RUN addgroup -g 1001 -S nodejs
|
||||
RUN adduser -S nextjs -u 1001
|
||||
|
|
@ -97,6 +97,9 @@ COPY --from=builder /app/public ./public
|
|||
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/typst ./typst
|
||||
|
||||
RUN mkdir -p /app/tmp && chown nextjs:nodejs /app/tmp
|
||||
|
||||
USER nextjs
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
protectedApiProcedure,
|
||||
publicProcedure,
|
||||
} from "~/server/api/trpc";
|
||||
import { TypstRunner, TypstRunner2 } from "~/server/services/typst.service";
|
||||
|
||||
export const testRouter = createTRPCRouter({
|
||||
testApi: protectedApiProcedure.query(async () => {
|
||||
|
|
@ -14,6 +15,9 @@ export const testRouter = createTRPCRouter({
|
|||
console.error("test console.error");
|
||||
throw new Error("This is a test error");
|
||||
}),
|
||||
testTypst: publicProcedure.query(async () => {
|
||||
return await TypstRunner2();
|
||||
}),
|
||||
});
|
||||
/*
|
||||
PROTECTED API CALL EXAMPLE
|
||||
|
|
|
|||
167
apps/infoalloggi/src/server/services/typst.service.ts
Normal file
167
apps/infoalloggi/src/server/services/typst.service.ts
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
import { exec, execSync } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
|
||||
import { db } from "~/server/db";
|
||||
import { getStringa } from "~/server/services/testi_stringhe.service";
|
||||
|
||||
const execPromise = promisify(exec);
|
||||
const projectRoot = path.resolve(".");
|
||||
const fontsPath = path.join(projectRoot, "typst", "fonts");
|
||||
|
||||
const tmpDir = path.join(projectRoot, "tmp");
|
||||
|
||||
type RicevutaProps = {
|
||||
ordineId: string;
|
||||
date: string;
|
||||
datetime: string;
|
||||
nominativo: string;
|
||||
codicefiscale: string;
|
||||
indirizzo: string;
|
||||
nazione: string;
|
||||
items: {
|
||||
title: string;
|
||||
desc: string | null;
|
||||
discount: number | null;
|
||||
amount: number;
|
||||
strike: boolean;
|
||||
}[];
|
||||
total: number;
|
||||
vat: number;
|
||||
method: string;
|
||||
};
|
||||
|
||||
export const TypstRunner = async () => {
|
||||
const timestamp = Date.now();
|
||||
await fs.mkdir(tmpDir, { recursive: true });
|
||||
const jsonFileName = `data-${timestamp}.json`;
|
||||
const jsonPath = path.join(tmpDir, jsonFileName);
|
||||
const templatePath = path.join(projectRoot, "typst", "receipt.typ");
|
||||
const pdfPath = path.join(tmpDir, `receipt-${timestamp}.pdf`);
|
||||
|
||||
const props: RicevutaProps = {
|
||||
ordineId: "dniuawndiandioawmodmawodmawdaw",
|
||||
date: "10 gennaio 2026",
|
||||
datetime: "14:55",
|
||||
nominativo: "Di Grigoli Angelo Damiano",
|
||||
codicefiscale: "DGRNLD97C13B602Z",
|
||||
indirizzo: "Via Aldo Fantina 15, PIEVE DEL GRAPPA (TV) 31017,",
|
||||
nazione: "Italia",
|
||||
items: [
|
||||
{
|
||||
title: "Acconto Cerco Transitorio",
|
||||
desc: null,
|
||||
discount: null,
|
||||
amount: 3500,
|
||||
strike: false,
|
||||
},
|
||||
{
|
||||
title: "Saldo Cerco Affitto Transitorio (6 mesi)",
|
||||
desc: "Pagamento differito, non è dovuto in questo momento",
|
||||
discount: null,
|
||||
amount: 37500,
|
||||
strike: true,
|
||||
},
|
||||
],
|
||||
total: 3500,
|
||||
vat: 3500 * 0.22,
|
||||
method: "Carta di credito",
|
||||
};
|
||||
|
||||
await fs.writeFile(jsonPath, JSON.stringify(props));
|
||||
|
||||
try {
|
||||
const command = `typst compile --input data_path=/tmp/${jsonFileName} --root "${projectRoot}" --font-path "${fontsPath}" "${templatePath}" "${pdfPath}"`;
|
||||
|
||||
await execPromise(command);
|
||||
|
||||
const pdfBuffer = await fs.readFile(pdfPath);
|
||||
return new Response(pdfBuffer, {
|
||||
headers: { "Content-Type": "application/pdf" },
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("typst compile failed:", err);
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to generate PDF with Typst",
|
||||
cause: err instanceof Error ? err : undefined,
|
||||
});
|
||||
} finally {
|
||||
// Cleanup — runs even on failure
|
||||
await Promise.all([
|
||||
fs.unlink(jsonPath).catch(() => {}),
|
||||
//fs.unlink(pdfFile).catch(() => {}),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
export const TypstRunner2 = async () => {
|
||||
const s = await getStringa({
|
||||
db: db,
|
||||
stringaId: "CONDIZIONI_CERCHI" as TestiEStringheStingaId,
|
||||
});
|
||||
if (!s || !s.stringa_value) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "Stringa non trovata",
|
||||
});
|
||||
}
|
||||
const timestamp = Date.now();
|
||||
await fs.mkdir(tmpDir, { recursive: true });
|
||||
const jsonFileName = `tmp-${timestamp}.json`;
|
||||
const jsonPath = path.join(tmpDir, jsonFileName);
|
||||
const templatePath = path.join(projectRoot, "typst", "testi.typ");
|
||||
const pdfFile = path.join(tmpDir, `doc-${timestamp}.pdf`);
|
||||
|
||||
const p = convertHtmlToTypst(s.stringa_value);
|
||||
|
||||
await fs.writeFile(jsonPath, JSON.stringify({ stringa: p }));
|
||||
|
||||
try {
|
||||
const command = `typst compile --input data_path=/tmp/${jsonFileName} --root "${projectRoot}" --font-path "${fontsPath}" "${templatePath}" "${pdfFile}"`;
|
||||
await execPromise(command);
|
||||
const pdfBuffer = await fs.readFile(pdfFile);
|
||||
return new Response(pdfBuffer, {
|
||||
headers: { "Content-Type": "application/pdf" },
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("typst compile failed:", err);
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to generate PDF with Typst",
|
||||
cause: err instanceof Error ? err : undefined,
|
||||
});
|
||||
} finally {
|
||||
// Cleanup — runs even on failure
|
||||
await Promise.all([
|
||||
fs.unlink(jsonPath).catch(() => {}),
|
||||
//fs.unlink(pdfFile).catch(() => {}),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts HTML to Typst markup using Pandoc.
|
||||
* works on Windows & Linux.
|
||||
*/
|
||||
function convertHtmlToTypst(html: string): string {
|
||||
try {
|
||||
// We pass the HTML via 'input' which writes to the process stdin
|
||||
const output = execSync(
|
||||
"pandoc -f html -t typst --syntax-highlighting=none",
|
||||
{
|
||||
input: html,
|
||||
encoding: "utf-8",
|
||||
},
|
||||
);
|
||||
return output;
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to convert HTML to Typst",
|
||||
cause: error instanceof Error ? error : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
139
apps/infoalloggi/typst/annuncio.typ
Normal file
139
apps/infoalloggi/typst/annuncio.typ
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#import "@preview/iconic-salmon-fa:1.1.0": fa-location-pin, fa-whatsapp
|
||||
#set page(
|
||||
paper: "a4",
|
||||
margin: (bottom: 1.5cm, rest: 1cm),
|
||||
footer: [
|
||||
#line(length: 100%, stroke: 0.5pt + luma(200))
|
||||
#set text(size: 8pt, fill: luma(100))
|
||||
#grid(
|
||||
columns: (1fr, auto),
|
||||
[Arcenia Srl. Tel. +39 0424529869 | Email: arca\@infoalloggi.it], [Documento generato in data: 31/3/2026],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
#set text(font: "Inter 18pt", size: 10pt, lang: "it", fill: rgb("#1f1f1f"))
|
||||
#set par(justify: true, leading: 0.65em)
|
||||
|
||||
// --- HEADER ---
|
||||
#grid(
|
||||
columns: (1fr, 1fr),
|
||||
align: (left, right),
|
||||
image("../public/Infoalloggi.png", width: 150pt, height: auto),
|
||||
|
||||
[
|
||||
*Arcenia Srl* \
|
||||
Via Beata Giovanna, 1 a Bassano del Grappa (VI) \
|
||||
Tel. +39 0424529869 \
|
||||
Email: arca\@infoalloggi.it
|
||||
],
|
||||
)
|
||||
#v(1em)
|
||||
#line(length: 100%, stroke: 0.5pt + luma(200))
|
||||
#v(1em)
|
||||
|
||||
// --- TITLE & REFERENCE ---
|
||||
#align(center)[#text(
|
||||
size: 16pt,
|
||||
weight: "bold",
|
||||
fill: rgb("#63493f"),
|
||||
)[BASSANO S. VITO MONOLOCALE A USO TRANSITORIO - 01/06/26]]
|
||||
#v(0.5em)
|
||||
|
||||
#align(center)[#text(
|
||||
size: 14pt,
|
||||
)[Riferimento: *3786B*]]
|
||||
|
||||
#v(1.5em)
|
||||
|
||||
#box(stroke: (paint: rgb("#25d366"), thickness: 1.5pt), width: 100%, inset: 16pt, radius: 10pt)[
|
||||
#grid(
|
||||
columns: 1fr,
|
||||
gutter: 14pt,
|
||||
[#fa-whatsapp(size: 20pt, fill: rgb("#25d366"))#h(1em)#text(
|
||||
size: 16pt,
|
||||
weight: "bold",
|
||||
fill: rgb("#63493f"),
|
||||
)[Contatti]#h(.5em)#text(size: 12pt, fill: rgb("#63493f"))[(mandare un messaggio WhatsApp prima di chiamare)]],
|
||||
[INFOALLOGGI (delegato)],
|
||||
[*Tel. +393453944827*]
|
||||
)
|
||||
]
|
||||
#v(1.5em)
|
||||
#grid(
|
||||
columns: 1fr,
|
||||
gutter: 10pt, [
|
||||
#text(size: 10pt, weight: "bold")[Indirizzo]], [
|
||||
#line(length: 100%, stroke: 0.5pt + luma(200))], [
|
||||
#text(weight: "bold", size: 12pt)[VIA MONTE ASOLONE, 21 int 1 Bassano del Grappa 36061 (VI)]#h(3em)#link(
|
||||
"https://maps.google.com",
|
||||
)[#fa-location-pin(size: 20pt, fill: red)#text(fill: blue)[Apri in Google Maps]]
|
||||
]
|
||||
)
|
||||
|
||||
#v(0.5em)
|
||||
|
||||
#v(0.5em)
|
||||
|
||||
|
||||
#v(1.5em)
|
||||
|
||||
// --- KEY DETAILS TABLE ---
|
||||
#table(
|
||||
columns: (1fr, 1fr, 1fr),
|
||||
stroke: 0.5pt + luma(200),
|
||||
inset: 10pt,
|
||||
[*Tipologia* \ Affitto Transitorio], [*Canone Mensile* \ 540,00 €], [*Disponibile da* \ 31/05/2026],
|
||||
)
|
||||
#v(1.5em)
|
||||
|
||||
// --- FEATURES ---
|
||||
#text(size: 12pt, weight: "bold")[Dotazioni e Servizi]
|
||||
#v(0.5em)
|
||||
#grid(
|
||||
columns: (1fr, 1fr, 1fr),
|
||||
gutter: 10pt,
|
||||
[- *Tipo Impianto:* Radiatori], [- *Riscaldamento:* Autonomo], [- *Arredi:* Arredato],
|
||||
[- *Piani Edificio:* Uno], [- *Internet:* No],
|
||||
)
|
||||
#v(1.5em)
|
||||
|
||||
// --- DESCRIPTION ---
|
||||
#text(size: 12pt, weight: "bold")[Descrizione]
|
||||
#v(0.5em)
|
||||
CODICE: 3786B LIBERO DA GIUGNO AFFITTO TRANSITORIO - Confortevole monolocale situato nella tranquilla zona di San Vito, a Bassano del Grappa. Libero da giugno, questo appartamento è la soluzione ideale per chi cerca un affitto transitorio.
|
||||
|
||||
Completamente ammobiliato e arredato con gusto, il monolocale si trova al piano terra e offre un soggiorno luminoso con un angolo cottura recente. La camera, dotata di un comodo letto alla francese, è separata da una parete per garantire privacy. Classe energetica F.
|
||||
|
||||
Il bagno è completo di doccia e lavatrice nuova, perfetto per le esigenze quotidiane. Inoltre, è disponibile un posto auto scoperto riservato, rendendo la vita ancora più comoda. Le utenze sono attive, e il contratto di affitto prevede una permanenza da 6 a 12 mesi, con un canone mensile di 540,00 € e spese condominiali di 80,00 €/mese.
|
||||
|
||||
Questo monolocale è ideale per single e si trova in un contesto tranquillo, lontano dal caos cittadino. Non è consentito fumare all'interno dei locali e si preferiscono inquilini senza animali. Non perdere l'occasione di vivere in un ambiente accogliente e funzionale! Contattaci per maggiori informazioni e per organizzare una visita.
|
||||
#v(1.5em)
|
||||
|
||||
// --- OWNER REQUESTS ---
|
||||
#rect(fill: rgb("#f5f5f5"), width: 100%, inset: 15pt, stroke: none)[
|
||||
#text(size: 12pt, weight: "bold")[RICHIESTE DEL PROPRIETARIO:]
|
||||
#v(0.5em)
|
||||
#list(
|
||||
[*Tipologia contratto di locazione:* TRANSITORIO (No stabile)],
|
||||
[*Regime fiscale:* CEDOLARE SECCA (Senza imposte di registro e bolli)],
|
||||
[*Data disponibilità:* 1 GIUGNO],
|
||||
[*Permanenza:* DA 6 a 12 MESI],
|
||||
[*Preferenza:* SINGLE],
|
||||
[*Contratto di lavoro:* A TEMPO DETERMINATO (o altra motivazione prevista per legge)],
|
||||
[*Canone mensile:* 540,00 €],
|
||||
[*Spese condominiali e accessorie:* 80,00 €/MESE],
|
||||
[*Deposito cauzionale:* IN BASE ALLA PERMANENZA],
|
||||
[*Consumi utenze:* Gas/Riscaldamento - Luce Acqua (Rimborso a CONSUMO)],
|
||||
[*Fumatori:* NON E' CONSENTITO FUMARE ALL'INTERNO DEI LOCALI],
|
||||
[*Animali:* NON E' CONSENTITO],
|
||||
)
|
||||
]
|
||||
#v(1.5em)
|
||||
|
||||
// --- HOW TO VISIT ---
|
||||
#text(size: 12pt, weight: "bold")[COSA FARE PER VISITARE L'IMMOBILE]
|
||||
#v(0.5em)
|
||||
Infoalloggi.it è una piattaforma immobiliare che ti consente di avere un contatto diretto con i proprietari previa attivazione del servizio. La piattaforma e i nostri referenti sono a tua disposizione per assisterti nella tua ricerca.
|
||||
|
||||
La geo-localizzazione indica la zona dell'immobile, l'indirizzo esatto lo ricevi con l'attivazione del servizio. Informazione strettamente riservata ed indirizzata all'Utente Arca-Infoalloggi nel rispetto dell' Art. F. Precisazione dei Tuoi Obblighi e responsabilità delle Condizioni Generali di Contratto (CGC) accettate.
|
||||
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-Black.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-Black.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-BlackItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-BlackItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-Bold.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-Bold.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-BoldItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-ExtraBold.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-ExtraBold.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-ExtraBoldItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-ExtraBoldItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-ExtraLight.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-ExtraLightItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-ExtraLightItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-Italic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-Italic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-Light.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-Light.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-LightItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-LightItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-Medium.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-Medium.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-MediumItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-MediumItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-Regular.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-Regular.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-SemiBold.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-SemiBold.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-SemiBoldItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-SemiBoldItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-Thin.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-Thin.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-ThinItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_18pt-ThinItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-Black.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-Black.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-BlackItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-BlackItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-Bold.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-Bold.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-BoldItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-ExtraBold.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-ExtraBold.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-ExtraBoldItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-ExtraBoldItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-ExtraLight.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-ExtraLightItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-ExtraLightItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-Italic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-Italic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-Light.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-Light.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-LightItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-LightItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-Medium.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-Medium.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-MediumItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-MediumItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-Regular.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-Regular.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-SemiBold.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-SemiBold.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-SemiBoldItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-SemiBoldItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-Thin.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-Thin.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-ThinItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_24pt-ThinItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-Black.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-Black.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-BlackItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-BlackItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-Bold.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-Bold.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-BoldItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-ExtraBold.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-ExtraBold.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-ExtraBoldItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-ExtraBoldItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-ExtraLight.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-ExtraLightItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-ExtraLightItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-Italic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-Italic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-Light.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-Light.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-LightItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-LightItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-Medium.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-Medium.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-MediumItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-MediumItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-Regular.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-Regular.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-SemiBold.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-SemiBold.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-SemiBoldItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-SemiBoldItalic.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-Thin.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-Thin.ttf
Normal file
Binary file not shown.
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-ThinItalic.ttf
Normal file
BIN
apps/infoalloggi/typst/fonts/Inter_28pt-ThinItalic.ttf
Normal file
Binary file not shown.
168
apps/infoalloggi/typst/receipt.typ
Normal file
168
apps/infoalloggi/typst/receipt.typ
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
#import "utils.typ": format-price
|
||||
|
||||
#let data_file = sys.inputs.at("data_path", default: "none")
|
||||
|
||||
#let data = if data_file != "none" { json(data_file) } else {
|
||||
(
|
||||
"ordineId": "3df6bd0d-2d2d-4687-b4b9-87f2aecc297c",
|
||||
"date": "1 gennaio 2026",
|
||||
"datetime": "14:55",
|
||||
"nominativo": "Di Grigoli Angelo Damiano",
|
||||
"codicefiscale": "DGRNLD97C13B602Z",
|
||||
"indirizzo": "Via Aldo Fantina 15, PIEVE DEL GRAPPA (TV) 31017",
|
||||
"nazione": "Italia",
|
||||
"items": (
|
||||
(
|
||||
"title": "Acconto Cerco Transitorio",
|
||||
"desc": none,
|
||||
"discount": none,
|
||||
"amount": 3500,
|
||||
"strike": false,
|
||||
),
|
||||
(
|
||||
"title": "Saldo Cerco Affitto Transitorio (6 mesi)",
|
||||
"desc": "Pagamento differito, non è dovuto in questo momento",
|
||||
"discount": none,
|
||||
"amount": 37500,
|
||||
"strike": true,
|
||||
),
|
||||
),
|
||||
"total": 3500,
|
||||
"vat": 770,
|
||||
"method": "Carta di credito",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#set page(
|
||||
paper: "a4",
|
||||
margin: (x: 1cm, y: 1cm),
|
||||
footer: [
|
||||
#set align(center)
|
||||
#set text(size: 9pt, fill: luma(100))
|
||||
Arcenia Srl.| P.IVA 00924740244 | Tel. +39 0424529869 | Email: arca\@infoalloggi.it
|
||||
],
|
||||
)
|
||||
|
||||
#set text(font: "Inter 18pt", size: 10pt)
|
||||
|
||||
// --- Header ---
|
||||
#grid(
|
||||
columns: (1fr, auto),
|
||||
[
|
||||
#text(size: 14pt, weight: "bold", fill: rgb("1a202c"))[Arcenia S.r.l.] \
|
||||
Via Beata Giovanna 1 \
|
||||
Bassano del Grappa (VI) 36061 \
|
||||
Italia
|
||||
],
|
||||
[
|
||||
#image("/public/favicon/favicon.png", width: 80pt)
|
||||
],
|
||||
)
|
||||
|
||||
#v(2.5em)
|
||||
|
||||
// --- Title ---
|
||||
#align(center)[
|
||||
#text(size: 16pt, weight: "bold", fill: rgb("1a202c"))[Ricevuta di cortesia]
|
||||
]
|
||||
|
||||
#v(2em)
|
||||
|
||||
#grid(
|
||||
columns: (1fr, auto),
|
||||
[
|
||||
#text(fill: luma(120), size: 9pt)[ID Ordine] \
|
||||
#data.ordineId
|
||||
],
|
||||
align(right)[
|
||||
#text(fill: luma(120), size: 9pt)[Data] \
|
||||
#data.date \
|
||||
#data.datetime
|
||||
],
|
||||
)
|
||||
|
||||
#v(0.5em)
|
||||
#line(length: 100%, stroke: 0.5pt + luma(200))
|
||||
#v(1em)
|
||||
|
||||
|
||||
#text(weight: "bold")[Intestatario:] \
|
||||
#v(0.2em)
|
||||
#data.nominativo \
|
||||
#data.codicefiscale \
|
||||
#data.indirizzo \
|
||||
#data.nazione
|
||||
#v(2.5em)
|
||||
|
||||
|
||||
#table(
|
||||
columns: (1fr, auto, auto),
|
||||
stroke: none,
|
||||
align: (left, right, right),
|
||||
inset: (x: 5pt, y: 10pt),
|
||||
|
||||
// Header row
|
||||
[*Oggetto*], [*Sconto applicato*], [*Importo*],
|
||||
table.hline(stroke: 0.5pt + luma(200)),
|
||||
|
||||
..data
|
||||
.items
|
||||
.map(item => {
|
||||
(
|
||||
item.title + if item.desc != none { "\n" + text(size: 9pt, fill: luma(100))[#item.desc] } else { "" },
|
||||
if item.at("discount", default: none) != none { [#item.discount %] } else [],
|
||||
if item.at("strike", default: false) {
|
||||
strike(stroke: 1pt + luma(100))[#text(fill: luma(100))[#format-price(item.amount)]]
|
||||
} else {
|
||||
[#format-price(item.amount)]
|
||||
},
|
||||
table.hline(stroke: 0.5pt + luma(200)),
|
||||
)
|
||||
})
|
||||
.flatten(),
|
||||
)
|
||||
|
||||
// --- Totals ---
|
||||
#v(0.5em)
|
||||
#align(right)[
|
||||
#grid(
|
||||
columns: (1fr, auto),
|
||||
align: (right, right),
|
||||
gutter: 1.5em,
|
||||
[*Totale:*], [*#format-price(data.total)*],
|
||||
[#text(fill: luma(100))[di cui IVA (22%):]], [#text(fill: luma(100))[#format-price(data.vat)]],
|
||||
)
|
||||
]
|
||||
|
||||
#v(2em)
|
||||
#line(length: 100%, stroke: 0.5pt + luma(200))
|
||||
#v(0.5em)
|
||||
|
||||
|
||||
#grid(
|
||||
columns: (1fr, auto),
|
||||
[
|
||||
#text(weight: "bold")[Metodo di pagamento] \
|
||||
#v(0.2em)
|
||||
#data.method
|
||||
],
|
||||
align(right)[
|
||||
#text(weight: "bold")[Status pagamento] \
|
||||
#v(0.2em)
|
||||
// Custom hex color for the success text
|
||||
#text(fill: rgb("00A650"), weight: "bold")[Pagato]
|
||||
],
|
||||
)
|
||||
|
||||
#v(0.5em)
|
||||
#line(length: 100%, stroke: 0.5pt + luma(200))
|
||||
#v(1em)
|
||||
|
||||
|
||||
#text(weight: "bold")[Note] \
|
||||
#v(0.2em)
|
||||
#text(size: 9pt, fill: rgb("4a5568"))[
|
||||
La presente non costituisce ricevuta o fattura fiscale ai sensi dell'Art.21, DPR 633/72.\
|
||||
Seguirà fattura elettronica direttamente al tuo indirizzo di posta.
|
||||
]
|
||||
23
apps/infoalloggi/typst/testi.typ
Normal file
23
apps/infoalloggi/typst/testi.typ
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
|
||||
|
||||
#let data = sys.inputs.at("data", default: none)
|
||||
|
||||
#set page(paper: "a4", margin: 1cm)
|
||||
#set text(font: "Inter 18pt", size: 10pt, lang: "it")
|
||||
#show heading: set text(fill: rgb("#1a202c"))
|
||||
#show heading.where(level: 2): set text(size: 14pt)
|
||||
#show heading.where(level: 3): set text(size: 12pt)
|
||||
#set par(justify: true, leading: 0.65em)
|
||||
|
||||
#let data_file = sys.inputs.at("data_path", default: "none")
|
||||
#let data = if data_file != "none" { json(data_file) } else {
|
||||
(
|
||||
"stringa": "none",
|
||||
)
|
||||
}
|
||||
|
||||
#eval(data.stringa, mode: "markup")
|
||||
|
||||
|
||||
|
||||
15
apps/infoalloggi/typst/utils.typ
Normal file
15
apps/infoalloggi/typst/utils.typ
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#import "@preview/oxifmt:1.0.0": strfmt
|
||||
|
||||
|
||||
#let format-price(cents) = {
|
||||
// Handle case where cents might be null or not a number
|
||||
if cents == none { return "0,00 €" }
|
||||
|
||||
let euros = int(cents / 100)
|
||||
let decimal = calc.rem(cents, 100)
|
||||
|
||||
// Create the decimal string and pad with a leading zero if needed (e.g., 5 cents -> "05")
|
||||
let decimal-str = if decimal < 10 { "0" + str(decimal) } else { str(decimal) }
|
||||
|
||||
return str(euros) + "," + decimal-str + " €"
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue