Merge branch <prisma> to <main> #6
6 changed files with 35 additions and 105 deletions
18
Dockerfile
18
Dockerfile
|
|
@ -1,32 +1,26 @@
|
||||||
FROM node:22-alpine AS base
|
FROM node:22-alpine AS base
|
||||||
|
|
||||||
# Dipendenze necessarie per Alpine (Prisma richiede OpenSSL)
|
|
||||||
RUN apk add --no-cache libc6-compat openssl
|
RUN apk add --no-cache libc6-compat openssl
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# --- STAGE 1: Installazione Dipendenze ---
|
# --- STAGE 1 ---
|
||||||
FROM base AS deps
|
FROM base AS deps
|
||||||
# Copia solo i file dei pacchetti per sfruttare la cache di Docker
|
|
||||||
COPY package.json ./
|
COPY package.json ./
|
||||||
# Usa 'npm ci' invece di 'install' per build riproducibili e più veloci
|
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
# --- STAGE 2: Build dell'applicazione ---
|
# --- STAGE 2 ---
|
||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
COPY --from=deps /app/node_modules ./node_modules
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Genera il client Prisma PRIMA del build
|
|
||||||
RUN npx prisma generate
|
RUN npx prisma generate
|
||||||
|
|
||||||
# Disabilita la telemetria di Next.js durante il build
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
# Esegue il build (che userà output: standalone)
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# --- STAGE 3: Immagine di Produzione (Runner) ---
|
# --- STAGE 3 ---
|
||||||
FROM base AS runner
|
FROM base AS runner
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
@ -35,20 +29,14 @@ ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
ENV HOSTNAME="0.0.0.0"
|
ENV HOSTNAME="0.0.0.0"
|
||||||
|
|
||||||
# Crea un utente non-root per sicurezza
|
|
||||||
RUN addgroup --system --gid 1001 nodejs
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
RUN adduser --system --uid 1001 nextjs
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
# Copia la cartella public (immagini, favicon, ecc.)
|
|
||||||
COPY --from=builder /app/public ./public
|
COPY --from=builder /app/public ./public
|
||||||
|
|
||||||
# --- GESTIONE PRISMA E STANDALONE ---
|
|
||||||
# Copia la cartella .next/standalone (il server ridotto)
|
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
# Copia gli asset statici (.next/static) nella posizione corretta
|
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
|
||||||
# Passa all'utente limitato
|
|
||||||
USER nextjs
|
USER nextjs
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ import ClientCard from "@/components/client-card";
|
||||||
import DeviceCard from "@/components/device-card";
|
import DeviceCard from "@/components/device-card";
|
||||||
import { Cliente, Intervento, Registratore } from "@/generated/prisma";
|
import { Cliente, Intervento, Registratore } from "@/generated/prisma";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, Suspense } from "react";
|
||||||
|
|
||||||
export default function Page() {
|
function ClientComponent() {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const id = searchParams.get("client");
|
const id = searchParams.get("client");
|
||||||
const [cliente, setCliente] = useState<Cliente>();
|
const [cliente, setCliente] = useState<Cliente>();
|
||||||
|
|
@ -23,85 +23,6 @@ export default function Page() {
|
||||||
getCliente();
|
getCliente();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
const clienti = [
|
|
||||||
{
|
|
||||||
name: "Savoldi Ettore",
|
|
||||||
email: "savoldi.ettore@gmail.com",
|
|
||||||
ragione_sociale: "Acconciature Uomo",
|
|
||||||
p_iva: "13407520172",
|
|
||||||
telefono: "0301547854",
|
|
||||||
sede: "Via Umberto I 60/T, Flero (BS)",
|
|
||||||
sede_url: "https://maps.app.goo.gl/9uNbw2a62ZCCjkQc7",
|
|
||||||
contratto: "https://google.com",
|
|
||||||
registratori: [
|
|
||||||
{
|
|
||||||
seriale: "80E100548745",
|
|
||||||
acquisto: "15/10/2019",
|
|
||||||
ultima_verifica: "15/10/2025",
|
|
||||||
prossima_verifica: "15/10/2026",
|
|
||||||
interventi: [
|
|
||||||
{
|
|
||||||
id: "0001",
|
|
||||||
data: "15/10/2025",
|
|
||||||
lavoro: "VERIFICA FISCALE - AGGIORNAMENTO FIRMWARE",
|
|
||||||
fattura: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "0002",
|
|
||||||
data: "28/05/2025",
|
|
||||||
lavoro: "SOSTITUZIONE DGFE",
|
|
||||||
fattura: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "0003",
|
|
||||||
data: "08/10/2024",
|
|
||||||
lavoro: "VERIFICA FISCALE",
|
|
||||||
fattura: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Forno Tagliaferri",
|
|
||||||
email: "info@tagliaferri.it",
|
|
||||||
ragione_sociale: "Forno Tagliaferri",
|
|
||||||
p_iva: "12901475639",
|
|
||||||
telefono: "0309183573",
|
|
||||||
sede: "Via Corso dei Martiri 11, Brescia (BS)",
|
|
||||||
sede_url: "https://maps.app.goo.gl/9uNbw2a62ZCCjkQc7",
|
|
||||||
contratto: "https://google.com",
|
|
||||||
registratori: [
|
|
||||||
{
|
|
||||||
seriale: "80E1002587545",
|
|
||||||
acquisto: "24/02/2020",
|
|
||||||
ultima_verifica: "24/02/2025",
|
|
||||||
prossima_verifica: "24/02/2026",
|
|
||||||
interventi: [
|
|
||||||
{
|
|
||||||
id: "0004",
|
|
||||||
data: "24/02/2025",
|
|
||||||
lavoro: "VERIFICA FISCALE",
|
|
||||||
fattura: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "0005",
|
|
||||||
data: "06/04/2025",
|
|
||||||
lavoro: "SOSTITUZIONE DGFE",
|
|
||||||
fattura: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "0006",
|
|
||||||
data: "24/02/2025",
|
|
||||||
lavoro: "VERIFICA FISCALE - AGGIORNAMENTO FIRMWARE",
|
|
||||||
fattura: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col gap-4 p-4">
|
<div className="flex flex-1 flex-col gap-4 p-4">
|
||||||
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
|
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
|
||||||
|
|
@ -117,3 +38,13 @@ export default function Page() {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<Suspense
|
||||||
|
fallback={<div className="p-8 text-center">Caricamento cliente...</div>}
|
||||||
|
>
|
||||||
|
<ClientComponent />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import {
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { useSearchParams } from "next/navigation";
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, Suspense } from "react";
|
||||||
import { Cliente } from "@/generated/prisma";
|
import { Cliente } from "@/generated/prisma";
|
||||||
|
|
||||||
export default function Page() {
|
function MapContent() {
|
||||||
const MapWithNoSSR = dynamic(() => import("../../components/map"), {
|
const MapWithNoSSR = dynamic(() => import("../../components/map"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
@ -32,3 +32,11 @@ export default function Page() {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return (
|
||||||
|
<Suspense fallback={<div>Caricamento mappa...</div>}>
|
||||||
|
<MapContent />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { Button } from "@/components/ui/button";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
const AddClientDialog = () => {
|
const AddClientDialog = () => {
|
||||||
|
const [open, setOpen] = useState<Boolean>(false);
|
||||||
const [nome, setNome] = useState("");
|
const [nome, setNome] = useState("");
|
||||||
const [ragione_sociale, setRagione_sociale] = useState("");
|
const [ragione_sociale, setRagione_sociale] = useState("");
|
||||||
const [partita_iva, setPartita_iva] = useState("");
|
const [partita_iva, setPartita_iva] = useState("");
|
||||||
|
|
@ -26,7 +27,7 @@ const AddClientDialog = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Dialog>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<form className="z-10">
|
<form className="z-10">
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="outline">Aggiungi cliente</Button>
|
<Button variant="outline">Aggiungi cliente</Button>
|
||||||
|
|
@ -118,8 +119,8 @@ const AddClientDialog = () => {
|
||||||
<Button variant="outline">Cancella</Button>
|
<Button variant="outline">Cancella</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
<Button
|
<Button
|
||||||
onClick={async () =>
|
onClick={async () => {
|
||||||
await fetch("/api/clienti", {
|
const res = await fetch("/api/clienti", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
nome: nome,
|
nome: nome,
|
||||||
|
|
@ -131,8 +132,12 @@ const AddClientDialog = () => {
|
||||||
sede_url: sede_url,
|
sede_url: sede_url,
|
||||||
contratto: contratto,
|
contratto: contratto,
|
||||||
}),
|
}),
|
||||||
})
|
});
|
||||||
|
|
||||||
|
if (res.status == 200) {
|
||||||
|
setOpen(false);
|
||||||
}
|
}
|
||||||
|
}}
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
Aggiungi
|
Aggiungi
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import { Button } from "@/components/ui/button";
|
||||||
import { Edit, Plus } from "lucide-react";
|
import { Edit, Plus } from "lucide-react";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useSearchParams } from "next/navigation";
|
|
||||||
import { Cliente, Registratore } from "@/generated/prisma";
|
import { Cliente, Registratore } from "@/generated/prisma";
|
||||||
import AddInterventoDialog from "./add-intervento";
|
import AddInterventoDialog from "./add-intervento";
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue