Refactor service document handling and contact modal
- Removed temp_tokens data from SQL initialization. - Updated AllegatiComp to invalidate additional service announcements on file removal. - Simplified imports in interactions.tsx and removed unused components. - Enhanced ServizioContent layout for better display of announcements. - Adjusted button styles in servizio_annunci_accordions for responsiveness. - Streamlined ricerca-table by removing unnecessary ServizioProvider wrapping. - Modified upload modal to support new upload callback structure. - Added new document reference fields to Servizio schema. - Introduced UsersStorageUserStorageId type for better type safety. - Updated servizio.controller to include document references in service announcements. - Changed storage.controller to return user_storage_id on conflict. - Updated docker-compose to streamline storage service configuration. - Created migration script for new foreign key constraints on document references. - Implemented ContattiProprietarioModal to manage document uploads and contact unlocking.
This commit is contained in:
parent
f3c4f231a9
commit
bd5a35a172
15 changed files with 582 additions and 403 deletions
|
|
@ -146,17 +146,16 @@ CREATE TABLE public.annunci (
|
|||
stato text,
|
||||
web boolean,
|
||||
caratteristiche json,
|
||||
url_immagini text[],
|
||||
homepage boolean,
|
||||
url_video text[],
|
||||
email text,
|
||||
creato_il timestamp without time zone,
|
||||
modificato_il timestamp without time zone,
|
||||
consegna integer,
|
||||
og_url text,
|
||||
disponibile_da date,
|
||||
permanenza integer[],
|
||||
persone text[]
|
||||
persone text[],
|
||||
updated_at timestamp without time zone
|
||||
);
|
||||
|
||||
|
||||
|
|
@ -394,6 +393,21 @@ CREATE TABLE public.flags (
|
|||
|
||||
ALTER TABLE public.flags OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: images_refs; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.images_refs (
|
||||
codice text NOT NULL,
|
||||
ordine integer NOT NULL,
|
||||
img text NOT NULL,
|
||||
thumb text NOT NULL,
|
||||
og_url text NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.images_refs OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: messages; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
|
@ -410,18 +424,6 @@ CREATE TABLE public.messages (
|
|||
|
||||
ALTER TABLE public.messages OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: miogest_images_ref; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.miogest_images_ref (
|
||||
codice text NOT NULL,
|
||||
foto text[]
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.miogest_images_ref OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: ordini; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
|
@ -526,7 +528,10 @@ CREATE TABLE public.servizio (
|
|||
permanenza integer,
|
||||
"isOkAcconto" boolean DEFAULT false NOT NULL,
|
||||
"isOkSaldo" boolean DEFAULT false NOT NULL,
|
||||
"isOkConsulenza" boolean DEFAULT false NOT NULL
|
||||
"isOkConsulenza" boolean DEFAULT false NOT NULL,
|
||||
doc_personale_fronte_ref uuid,
|
||||
doc_personale_retro_ref uuid,
|
||||
doc_motivazione_ref uuid
|
||||
);
|
||||
|
||||
|
||||
|
|
@ -541,7 +546,7 @@ CREATE TABLE public.servizio_annunci (
|
|||
annunci_id integer NOT NULL,
|
||||
created_at timestamp without time zone DEFAULT now() NOT NULL,
|
||||
open_contatti_at timestamp without time zone,
|
||||
doc_conferma_ref uuid,
|
||||
doc_conferma_ref text,
|
||||
user_confirmed_at timestamp without time zone,
|
||||
accettato_conferma_at timestamp without time zone,
|
||||
caparra_causale text,
|
||||
|
|
@ -549,12 +554,12 @@ CREATE TABLE public.servizio_annunci (
|
|||
caparra_importo text,
|
||||
caparra_intestazione text,
|
||||
"hasConfermaAdmin" boolean DEFAULT false NOT NULL,
|
||||
doc_contratto_ref uuid,
|
||||
doc_contratto_ref text,
|
||||
gestionale_id integer,
|
||||
contratto_decorrenza date,
|
||||
contratto_scadenza date,
|
||||
contratto_tipo text,
|
||||
doc_registrazione_ref uuid,
|
||||
doc_registrazione_ref text,
|
||||
doc_conferma_added boolean DEFAULT false NOT NULL,
|
||||
doc_contratto_added boolean DEFAULT false NOT NULL,
|
||||
doc_registrazione_added boolean DEFAULT false NOT NULL
|
||||
|
|
@ -575,34 +580,6 @@ CREATE TABLE public.servizio_interessi (
|
|||
|
||||
ALTER TABLE public.servizio_interessi OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: storageindex; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.storageindex (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
created_at timestamp without time zone DEFAULT now() NOT NULL,
|
||||
filename text,
|
||||
ext text,
|
||||
from_admin boolean DEFAULT false NOT NULL,
|
||||
expires_at timestamp without time zone
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.storageindex OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: temp_tokens; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.temp_tokens (
|
||||
token uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
elapse timestamp without time zone DEFAULT (now() + '00:00:30'::interval) NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.temp_tokens OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: testi_e_stringhe; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
|
@ -677,8 +654,10 @@ ALTER TABLE public.users_anagrafica OWNER TO postgres;
|
|||
--
|
||||
|
||||
CREATE TABLE public.users_storage (
|
||||
userid uuid NOT NULL,
|
||||
storageid uuid NOT NULL
|
||||
"userId" uuid NOT NULL,
|
||||
"storageId" text NOT NULL,
|
||||
from_admin boolean DEFAULT false NOT NULL,
|
||||
user_storage_id uuid DEFAULT gen_random_uuid() NOT NULL
|
||||
);
|
||||
|
||||
|
||||
|
|
@ -791,6 +770,14 @@ ALTER TABLE ONLY public.event_queue
|
|||
ADD CONSTRAINT event_queue_pkey PRIMARY KEY (event_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: images_refs img_cod_order; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.images_refs
|
||||
ADD CONSTRAINT img_cod_order UNIQUE (codice, ordine);
|
||||
|
||||
|
||||
--
|
||||
-- Name: messages messages_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
|
@ -799,14 +786,6 @@ ALTER TABLE ONLY public.messages
|
|||
ADD CONSTRAINT messages_pkey PRIMARY KEY (messageid);
|
||||
|
||||
|
||||
--
|
||||
-- Name: miogest_images_ref miogest_images_ref_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.miogest_images_ref
|
||||
ADD CONSTRAINT miogest_images_ref_pkey PRIMARY KEY (codice);
|
||||
|
||||
|
||||
--
|
||||
-- Name: ordini ordini2_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
|
@ -847,30 +826,6 @@ ALTER TABLE ONLY public.flags
|
|||
ADD CONSTRAINT sitesettings_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users_storage sotrageuser; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users_storage
|
||||
ADD CONSTRAINT sotrageuser UNIQUE (userid, storageid);
|
||||
|
||||
|
||||
--
|
||||
-- Name: storageindex storageindex_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.storageindex
|
||||
ADD CONSTRAINT storageindex_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: temp_tokens temp_tokens_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.temp_tokens
|
||||
ADD CONSTRAINT temp_tokens_pkey PRIMARY KEY (token);
|
||||
|
||||
|
||||
--
|
||||
-- Name: testi_e_stringhe testi_e_stringhe_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
|
@ -895,6 +850,14 @@ ALTER TABLE ONLY public.servizio_annunci
|
|||
ADD CONSTRAINT uniq_annuncio_servizio UNIQUE (annunci_id, servizio_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users_storage unique_user_storage; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users_storage
|
||||
ADD CONSTRAINT unique_user_storage UNIQUE ("userId", "storageId");
|
||||
|
||||
|
||||
--
|
||||
-- Name: servizio_interessi uniqui_interest; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
|
@ -919,6 +882,14 @@ ALTER TABLE ONLY public.users
|
|||
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users_storage users_storage_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users_storage
|
||||
ADD CONSTRAINT users_storage_pkey PRIMARY KEY (user_storage_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: users_anagrafica usersanagrafica2_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
|
@ -935,13 +906,6 @@ ALTER TABLE ONLY public.users_anagrafica
|
|||
ADD CONSTRAINT usserunique2 UNIQUE (userid);
|
||||
|
||||
|
||||
--
|
||||
-- Name: annunci_immagini_index; Type: INDEX; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE INDEX annunci_immagini_index ON public.annunci USING gin (url_immagini);
|
||||
|
||||
|
||||
--
|
||||
-- Name: ratelimiter_key_index; Type: INDEX; Schema: public; Owner: postgres
|
||||
--
|
||||
|
|
@ -957,6 +921,14 @@ ALTER TABLE ONLY public.chats_etichette
|
|||
ADD CONSTRAINT chat_etichetta_fkey FOREIGN KEY (chatid) REFERENCES public.chats(chatid) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
|
||||
--
|
||||
-- Name: images_refs cod_image; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.images_refs
|
||||
ADD CONSTRAINT cod_image FOREIGN KEY (codice) REFERENCES public.annunci(codice);
|
||||
|
||||
|
||||
--
|
||||
-- Name: emails email_user; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
|
@ -1070,27 +1042,27 @@ ALTER TABLE ONLY public.servizio_annunci
|
|||
|
||||
|
||||
--
|
||||
-- Name: servizio_annunci servizio_annuncio_conferma_doc; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
-- Name: servizio servizio_doc_fronte; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.servizio_annunci
|
||||
ADD CONSTRAINT servizio_annuncio_conferma_doc FOREIGN KEY (doc_conferma_ref) REFERENCES public.storageindex(id) ON UPDATE CASCADE ON DELETE SET NULL NOT VALID;
|
||||
ALTER TABLE ONLY public.servizio
|
||||
ADD CONSTRAINT servizio_doc_fronte FOREIGN KEY (doc_personale_fronte_ref) REFERENCES public.users_storage(user_storage_id) ON UPDATE CASCADE ON DELETE SET NULL NOT VALID;
|
||||
|
||||
|
||||
--
|
||||
-- Name: servizio_annunci servizio_annuncio_contratto_doc; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
-- Name: servizio servizio_doc_motivazione; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.servizio_annunci
|
||||
ADD CONSTRAINT servizio_annuncio_contratto_doc FOREIGN KEY (doc_contratto_ref) REFERENCES public.storageindex(id) ON UPDATE CASCADE ON DELETE SET NULL NOT VALID;
|
||||
ALTER TABLE ONLY public.servizio
|
||||
ADD CONSTRAINT servizio_doc_motivazione FOREIGN KEY (doc_motivazione_ref) REFERENCES public.users_storage(user_storage_id) ON UPDATE CASCADE ON DELETE SET NULL NOT VALID;
|
||||
|
||||
|
||||
--
|
||||
-- Name: servizio_annunci servizio_annuncio_registraz_doc; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
-- Name: servizio servizio_doc_retro; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.servizio_annunci
|
||||
ADD CONSTRAINT servizio_annuncio_registraz_doc FOREIGN KEY (doc_registrazione_ref) REFERENCES public.storageindex(id) ON UPDATE CASCADE ON DELETE SET NULL NOT VALID;
|
||||
ALTER TABLE ONLY public.servizio
|
||||
ADD CONSTRAINT servizio_doc_retro FOREIGN KEY (doc_personale_retro_ref) REFERENCES public.users_storage(user_storage_id) ON UPDATE CASCADE ON DELETE SET NULL NOT VALID;
|
||||
|
||||
|
||||
--
|
||||
|
|
@ -1117,20 +1089,12 @@ ALTER TABLE ONLY public.users_anagrafica
|
|||
ADD CONSTRAINT useridfkeyanagrafica2 FOREIGN KEY (userid) REFERENCES public.users(id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
|
||||
|
||||
|
||||
--
|
||||
-- Name: users_storage users_storage_storageid_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users_storage
|
||||
ADD CONSTRAINT users_storage_storageid_fkey FOREIGN KEY (storageid) REFERENCES public.storageindex(id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
|
||||
|
||||
|
||||
--
|
||||
-- Name: users_storage userstorage; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.users_storage
|
||||
ADD CONSTRAINT userstorage FOREIGN KEY (userid) REFERENCES public.users(id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
ADD CONSTRAINT userstorage FOREIGN KEY ("userId") REFERENCES public.users(id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
|
||||
--
|
||||
|
|
|
|||
|
|
@ -87,17 +87,6 @@ SALDOCA500+ 38700 CONDIZIONI_CERCHI t Saldo Cerco Stabile (500+) Stable 500+ Sal
|
|||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: temp_tokens; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
COPY public.temp_tokens (token, elapse) FROM stdin;
|
||||
c256c17d-3299-47a4-b3ef-640aa9688b38 2025-08-22 13:01:41.20164
|
||||
4bd62a5f-aa6d-403b-a1bb-c0e11e382adc 2025-08-22 13:01:48.897271
|
||||
5d069230-9572-4b26-aa06-2964f3ca79a2 2030-02-19 10:25:33.046587
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: testi_e_stringhe; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
|
|
|||
20
apps/infoalloggi/migration.sql
Normal file
20
apps/infoalloggi/migration.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
ALTER TABLE
|
||||
IF EXISTS public.servizio
|
||||
ADD
|
||||
CONSTRAINT servizio_doc_fronte FOREIGN KEY (doc_personale_fronte_ref) REFERENCES public.users_storage ("storageId") MATCH SIMPLE ON UPDATE CASCADE ON DELETE
|
||||
SET
|
||||
NULL NOT VALID;
|
||||
|
||||
ALTER TABLE
|
||||
IF EXISTS public.servizio
|
||||
ADD
|
||||
CONSTRAINT servizio_doc_retro FOREIGN KEY (doc_personale_retro_ref) REFERENCES public.users_storage ("storageId") MATCH SIMPLE ON UPDATE CASCADE ON DELETE
|
||||
SET
|
||||
NULL NOT VALID;
|
||||
|
||||
ALTER TABLE
|
||||
IF EXISTS public.servizio
|
||||
ADD
|
||||
CONSTRAINT servizio_doc_motivazione FOREIGN KEY (doc_motivazione_ref) REFERENCES public.users_storage ("storageId") MATCH SIMPLE ON UPDATE CASCADE ON DELETE
|
||||
SET
|
||||
NULL NOT VALID;
|
||||
|
|
@ -66,6 +66,8 @@ export const AllegatiComp = ({
|
|||
await utils.storage.retrieveUserFileData.invalidate({
|
||||
userId: userId,
|
||||
});
|
||||
await utils.servizio.getAllServizioAnnunci.invalidate({ userId });
|
||||
|
||||
toast.success(t.allegati.allegato_rimosso, {
|
||||
icon: "🗑️",
|
||||
id: context?.toastId,
|
||||
|
|
|
|||
|
|
@ -1,18 +1,14 @@
|
|||
import {
|
||||
CircleCheck,
|
||||
CircleUser,
|
||||
FileBadge,
|
||||
FileText,
|
||||
Handshake,
|
||||
Navigation,
|
||||
PackageCheck,
|
||||
Phone,
|
||||
Pointer,
|
||||
TriangleAlert,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import {
|
||||
Credenza,
|
||||
|
|
@ -26,7 +22,6 @@ import {
|
|||
CredenzaTrigger,
|
||||
} from "~/components/custom_ui/credenza";
|
||||
import LoadingButton from "~/components/custom_ui/loading-button";
|
||||
import { LoadingPage } from "~/components/loading";
|
||||
import { Button, type ButtonConfigs } from "~/components/ui/button";
|
||||
import {
|
||||
Tooltip,
|
||||
|
|
@ -39,7 +34,7 @@ import { useTranslation } from "~/providers/I18nProvider";
|
|||
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
||||
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||
import { api } from "~/utils/api";
|
||||
import { WhatsAppIcon2 } from "../svgs";
|
||||
import { ContattiProprietarioModal } from "./modale_contatti";
|
||||
|
||||
export const Interactions = () => {
|
||||
const { servizio, servizioId } = useServizio();
|
||||
|
|
@ -406,225 +401,3 @@ const ConfermaLavorazioneModal = () => {
|
|||
</Credenza>
|
||||
);
|
||||
};
|
||||
|
||||
const ContattiProprietarioModal = () => {
|
||||
const { servizioId, userId } = useServizio();
|
||||
const { data, annuncioId } = useServizioAnnuncio();
|
||||
const { t } = useTranslation();
|
||||
const [showContacts, setShowContacts] = useState<boolean>(
|
||||
data.open_contatti_at != null,
|
||||
);
|
||||
const utils = api.useUtils();
|
||||
|
||||
const { mutate } = api.servizio.unlockServizioContatti.useMutation({
|
||||
onError: (err) => {
|
||||
toast.error(err.message, {
|
||||
id: "unlock-contacts",
|
||||
});
|
||||
},
|
||||
onMutate: () => {
|
||||
toast.loading("Caricamento ...", {
|
||||
id: "unlock-contacts",
|
||||
});
|
||||
},
|
||||
onSuccess: async (data) => {
|
||||
if (!data) {
|
||||
toast.error("Limite di sblocchi raggiunto", {
|
||||
id: "unlock-contacts",
|
||||
});
|
||||
return;
|
||||
}
|
||||
toast.success("Successo", {
|
||||
id: "unlock-contacts",
|
||||
});
|
||||
await utils.servizio.getAllServizioAnnunci.invalidate({
|
||||
userId,
|
||||
});
|
||||
setShowContacts(true);
|
||||
},
|
||||
});
|
||||
|
||||
const d = new Date();
|
||||
const tooEarly = d.getHours() < 9;
|
||||
const tooLate = d.getHours() >= 20;
|
||||
|
||||
return (
|
||||
<Credenza>
|
||||
<CredenzaTrigger asChild>
|
||||
{showContacts ? (
|
||||
<Button
|
||||
aria-label="Vedi contatti proprietario"
|
||||
className="flex w-fit items-center gap-2"
|
||||
variant="success"
|
||||
>
|
||||
<CircleUser className="size-7" />
|
||||
Vedi i contatti
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
aria-label="Contatta proprietario"
|
||||
className="flex w-fit items-center gap-2"
|
||||
variant="warning"
|
||||
>
|
||||
<CircleUser className="size-7" />
|
||||
Ottieni i contatti
|
||||
</Button>
|
||||
)}
|
||||
</CredenzaTrigger>
|
||||
<CredenzaContent className="max-h-[90vh]">
|
||||
<CredenzaHeader>
|
||||
<CredenzaTitle>{t.contatto.title}</CredenzaTitle>
|
||||
<CredenzaDescription className="sr-only">
|
||||
{t.contatto.descrizione}
|
||||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5">
|
||||
{showContacts ? (
|
||||
<ContattiProprietarioData />
|
||||
) : (
|
||||
<div className="space-y-5">
|
||||
<p>{t.contatto.disclaimer}</p>
|
||||
<p>{t.contatto.disclaimer2}</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{t.contatto.disclaimer3}
|
||||
</p>
|
||||
{(tooEarly || tooLate) && (
|
||||
<div className="flex items-center gap-2 rounded-md bg-yellow-200 p-2">
|
||||
<TriangleAlert className="size-8" />
|
||||
{/*TODO testare */}
|
||||
<p>
|
||||
Il proprietario riceverà la notifica alle ore 9.00{" "}
|
||||
{tooLate && "di domani"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
{!showContacts && (
|
||||
<Button
|
||||
aria-label="Contatta proprietario"
|
||||
onClick={() =>
|
||||
mutate({
|
||||
annuncioId,
|
||||
servizioId,
|
||||
})
|
||||
}
|
||||
variant="success"
|
||||
>
|
||||
{t.accetta}
|
||||
</Button>
|
||||
)}
|
||||
<CredenzaClose asChild>
|
||||
<Button aria-label="Chiudi modal credenza" variant="outline">
|
||||
{t.chiudi}
|
||||
</Button>
|
||||
</CredenzaClose>
|
||||
</CredenzaFooter>
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
);
|
||||
};
|
||||
|
||||
const ContattiProprietarioData = () => {
|
||||
const { servizioId } = useServizio();
|
||||
const { annuncioId } = useServizioAnnuncio();
|
||||
const { data, isLoading } = api.annunci.getProprietarioData.useQuery({
|
||||
annuncioId,
|
||||
servizioId,
|
||||
});
|
||||
const [navigationUrl, setNavigationUrl] = useState<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
if (!data || !data.propData) return;
|
||||
if (typeof window === "undefined") return;
|
||||
if (
|
||||
navigator.userAgent.toUpperCase().includes("MAC") ||
|
||||
navigator.userAgent.toUpperCase().includes("IPAD") ||
|
||||
navigator.userAgent.toUpperCase().includes("IPHONE") ||
|
||||
navigator.userAgent.toUpperCase().includes("IOS") ||
|
||||
navigator.userAgent.toUpperCase().includes("IPOD")
|
||||
) {
|
||||
setNavigationUrl(
|
||||
`maps://www.google.com/maps/dir/?api=1&travelmode=driving&layer=traffic&destination=${data.propData.lat},${data.propData.lon}`,
|
||||
);
|
||||
} else {
|
||||
setNavigationUrl(
|
||||
`https://www.google.com/maps/dir/?api=1&travelmode=driving&layer=traffic&destination=${data.propData.lat},${data.propData.lon}`,
|
||||
);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<LoadingPage />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!data || !data.propData) {
|
||||
return <p>{t.contatto.error}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="font-semibold">{t.contatto.propietario}:</h3>
|
||||
<p>
|
||||
{t.contatto.nome}: {data.propData.locatore}
|
||||
</p>
|
||||
<a
|
||||
className="flex items-center gap-2"
|
||||
href={`tel:${data.propData.numero}`}
|
||||
>
|
||||
<Phone />:
|
||||
<span className="text-blue-600 underline">{data.propData.numero}</span>
|
||||
<Link href={`https://wa.me/${data.propData.numero}`} target="_blank">
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
size="sm"
|
||||
variant="success"
|
||||
>
|
||||
<WhatsAppIcon2 className="size-4 fill-white" />
|
||||
WhatsApp
|
||||
</Button>
|
||||
</Link>
|
||||
</a>
|
||||
<br />
|
||||
<h3 className="font-semibold">{t.contatto.immobile}:</h3>
|
||||
<p>
|
||||
{t.contatto.indirizzo}: {data.propData.indirizzo},{" "}
|
||||
{data.propData.civico} {data.propData.comune} ({data.propData.provincia}
|
||||
)
|
||||
</p>
|
||||
<a
|
||||
className="flex w-fit items-center gap-2 rounded-md border border-border px-3 py-2"
|
||||
href={navigationUrl}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Navigation /> <span>{t.contatto.apri_nav}</span>
|
||||
</a>
|
||||
<br />
|
||||
<p>
|
||||
{t.contatto.sbloccato_il}:{" "}
|
||||
{data.opened_at.toLocaleString("it", {
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
})}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 rounded-md bg-red-500 p-2 text-white">
|
||||
<TriangleAlert className="size-8" />
|
||||
<div>
|
||||
<p>{t.contatto.contatta}</p>
|
||||
<p className="font-semibold">{t.contatto.orari}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import {
|
|||
import { api } from "~/utils/api";
|
||||
import {
|
||||
AnnunciCompatibili,
|
||||
AnnunciInConferma,
|
||||
AnnunciSelezionati,
|
||||
} from "./servizio_annunci_accordions";
|
||||
|
||||
|
|
@ -255,12 +254,22 @@ export const ServizioContent = () => {
|
|||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2 overflow-y-auto">
|
||||
{annunciInConferma.map((data) => {
|
||||
return (
|
||||
<ServizioAnnuncioProvider data={data} key={data.id}>
|
||||
<AnnuncioCard className="border-2 border-green-500" />
|
||||
</ServizioAnnuncioProvider>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<AnnunciInConferma annunci={annunciInConferma} />
|
||||
<div className="flex flex-col items-center gap-4 sm:flex-row">
|
||||
<AnnunciSelezionati annunci={annunciNotInConferma} />
|
||||
<AnnunciCompatibili />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
header={
|
||||
|
|
|
|||
378
apps/infoalloggi/src/components/servizio/modale_contatti.tsx
Normal file
378
apps/infoalloggi/src/components/servizio/modale_contatti.tsx
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
import {
|
||||
CircleCheck,
|
||||
CircleUser,
|
||||
Navigation,
|
||||
Phone,
|
||||
TriangleAlert,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useMemo } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "~/providers/I18nProvider";
|
||||
import { useServizio, useServizioAnnuncio } from "~/providers/ServizioProvider";
|
||||
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
||||
import { api } from "~/utils/api";
|
||||
import {
|
||||
Credenza,
|
||||
CredenzaBody,
|
||||
CredenzaClose,
|
||||
CredenzaContent,
|
||||
CredenzaDescription,
|
||||
CredenzaFooter,
|
||||
CredenzaHeader,
|
||||
CredenzaTitle,
|
||||
CredenzaTrigger,
|
||||
} from "../custom_ui/credenza";
|
||||
import { LoadingPage } from "../loading";
|
||||
import { WhatsAppIcon2 } from "../svgs";
|
||||
import { Button } from "../ui/button";
|
||||
import { type UploadCallback, UploadModal } from "../upload_modal";
|
||||
|
||||
export const ContattiProprietarioModal = () => {
|
||||
const { servizio } = useServizio();
|
||||
const { data } = useServizioAnnuncio();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Credenza>
|
||||
<CredenzaTrigger asChild>
|
||||
{data.open_contatti_at != null ? (
|
||||
<Button
|
||||
aria-label="Vedi contatti proprietario"
|
||||
className="flex w-fit items-center gap-2"
|
||||
variant="success"
|
||||
>
|
||||
<CircleUser className="size-7" />
|
||||
Vedi i contatti
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
aria-label="Contatta proprietario"
|
||||
className="flex w-fit items-center gap-2"
|
||||
variant="warning"
|
||||
>
|
||||
<CircleUser className="size-7" />
|
||||
Ottieni i contatti
|
||||
</Button>
|
||||
)}
|
||||
</CredenzaTrigger>
|
||||
<CredenzaContent className="max-h-[90vh]">
|
||||
<CredenzaHeader>
|
||||
<CredenzaTitle>{t.contatto.title}</CredenzaTitle>
|
||||
<CredenzaDescription className="sr-only">
|
||||
{t.contatto.descrizione}
|
||||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
{servizio.doc_personale_fronte == null ||
|
||||
servizio.doc_personale_retro == null ? (
|
||||
<DocCheckpoint />
|
||||
// biome-ignore lint/style/noNestedTernary: <this is fine>
|
||||
) : data.open_contatti_at == null ? (
|
||||
<PreUnlock />
|
||||
) : (
|
||||
<PostUnlock />
|
||||
)}
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
);
|
||||
};
|
||||
|
||||
const DocComponent = ({
|
||||
docInfo,
|
||||
cb,
|
||||
}: {
|
||||
docInfo: {
|
||||
storageId: string;
|
||||
ref: UsersStorageUserStorageId | null;
|
||||
} | null;
|
||||
cb: UploadCallback;
|
||||
}) => {
|
||||
const { isAdmin, userId } = useServizio();
|
||||
|
||||
if (!docInfo?.storageId) {
|
||||
return <UploadModal cb_onUpload={cb} isAdmin={isAdmin} userId={userId} />;
|
||||
}
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-green-500">
|
||||
<CircleCheck className="size-5" />
|
||||
<span>Documento caricato</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DocCheckpoint = () => {
|
||||
const { t } = useTranslation();
|
||||
const { userId, servizio } = useServizio();
|
||||
const utils = api.useUtils();
|
||||
const { mutate } = api.servizio.updateServizio.useMutation({
|
||||
onSuccess: async () => {
|
||||
await utils.servizio.getAllServizioAnnunci.invalidate({
|
||||
userId,
|
||||
});
|
||||
await utils.storage.retrieveUserFileData.invalidate({ userId });
|
||||
await utils.servizio.getServizio.invalidate({
|
||||
servizioId: servizio.servizio_id,
|
||||
});
|
||||
},
|
||||
onError: (err) => {
|
||||
toast.error(`Errore aggiornamento servizio: ${err.message}`);
|
||||
},
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5">
|
||||
<div className="space-y-5">
|
||||
<p>Devi ancora caricare un tuo documento</p>
|
||||
<div className="flex items-center gap-2">
|
||||
Fronte documento:{" "}
|
||||
<DocComponent
|
||||
cb={({ userStorageIds }) =>
|
||||
userStorageIds.length > 0 &&
|
||||
mutate({
|
||||
servizioId: servizio.servizio_id,
|
||||
data: {
|
||||
doc_personale_fronte_ref: userStorageIds[0],
|
||||
},
|
||||
})
|
||||
}
|
||||
docInfo={servizio.doc_personale_fronte}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
Retro documento:{" "}
|
||||
<DocComponent
|
||||
cb={({ userStorageIds }) =>
|
||||
userStorageIds.length > 0 &&
|
||||
mutate({
|
||||
servizioId: servizio.servizio_id,
|
||||
data: {
|
||||
doc_personale_retro_ref: userStorageIds[0],
|
||||
},
|
||||
})
|
||||
}
|
||||
docInfo={servizio.doc_personale_retro}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<p className="text-muted-foreground text-sm">
|
||||
I tuoi dati saranno conservati solamente per la durata del servizio,
|
||||
in conformità con la nostra{" "}
|
||||
<Link className="underline" href="/privacy-policy" target="_blank">
|
||||
Informativa sulla Privacy
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
<CredenzaClose asChild>
|
||||
<Button aria-label="Chiudi modal credenza" variant="outline">
|
||||
{t.chiudi}
|
||||
</Button>
|
||||
</CredenzaClose>
|
||||
</CredenzaFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const PreUnlock = () => {
|
||||
const { userId, servizioId } = useServizio();
|
||||
const { annuncioId } = useServizioAnnuncio();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const utils = api.useUtils();
|
||||
|
||||
const { mutate } = api.servizio.unlockServizioContatti.useMutation({
|
||||
onError: (err) => {
|
||||
toast.error(err.message, {
|
||||
id: "unlock-contacts",
|
||||
});
|
||||
},
|
||||
onMutate: () => {
|
||||
toast.loading("Caricamento ...", {
|
||||
id: "unlock-contacts",
|
||||
});
|
||||
},
|
||||
onSuccess: async (data) => {
|
||||
if (!data) {
|
||||
toast.error("Limite di sblocchi raggiunto", {
|
||||
id: "unlock-contacts",
|
||||
});
|
||||
return;
|
||||
}
|
||||
toast.success("Successo", {
|
||||
id: "unlock-contacts",
|
||||
});
|
||||
await utils.servizio.getAllServizioAnnunci.invalidate({
|
||||
userId,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const d = new Date();
|
||||
const tooEarly = d.getHours() < 9;
|
||||
const tooLate = d.getHours() >= 20;
|
||||
return (
|
||||
<>
|
||||
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5">
|
||||
<div className="space-y-5">
|
||||
<p>{t.contatto.disclaimer}</p>
|
||||
<p>{t.contatto.disclaimer2}</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{t.contatto.disclaimer3}
|
||||
</p>
|
||||
{(tooEarly || tooLate) && (
|
||||
<div className="flex items-center gap-2 rounded-md bg-yellow-200 p-2">
|
||||
<TriangleAlert className="size-8" />
|
||||
{/*TODO testare */}
|
||||
<p>
|
||||
Il proprietario riceverà la notifica alle ore 9.00{" "}
|
||||
{tooLate && "di domani"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
<Button
|
||||
aria-label="Contatta proprietario"
|
||||
onClick={() =>
|
||||
mutate({
|
||||
annuncioId,
|
||||
servizioId,
|
||||
})
|
||||
}
|
||||
variant="success"
|
||||
>
|
||||
{t.accetta}
|
||||
</Button>
|
||||
|
||||
<CredenzaClose asChild>
|
||||
<Button aria-label="Chiudi modal credenza" variant="outline">
|
||||
{t.chiudi}
|
||||
</Button>
|
||||
</CredenzaClose>
|
||||
</CredenzaFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function getNavigationUrl(lat: string, lon: string): string {
|
||||
if (typeof window !== "undefined") {
|
||||
const isIOS =
|
||||
navigator.userAgent.toUpperCase().includes("MAC") ||
|
||||
navigator.userAgent.toUpperCase().includes("IPAD") ||
|
||||
navigator.userAgent.toUpperCase().includes("IPHONE") ||
|
||||
navigator.userAgent.toUpperCase().includes("IOS") ||
|
||||
navigator.userAgent.toUpperCase().includes("IPOD");
|
||||
|
||||
return `${isIOS ? "maps" : "https"}://www.google.com/maps/dir/?api=1&travelmode=driving&layer=traffic&destination=${lat},${lon}`;
|
||||
}
|
||||
return `https://www.google.com/maps/dir/?api=1&travelmode=driving&layer=traffic&destination=${lat},${lon}`;
|
||||
}
|
||||
|
||||
const PostUnlock = () => {
|
||||
const { servizioId } = useServizio();
|
||||
const { annuncioId } = useServizioAnnuncio();
|
||||
const { data, isLoading } = api.annunci.getProprietarioData.useQuery({
|
||||
annuncioId,
|
||||
servizioId,
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Memoize navigation URL
|
||||
const navigationUrl = useMemo(() => {
|
||||
if (!data?.propData || !data.propData.lat || !data.propData.lon)
|
||||
return undefined;
|
||||
return getNavigationUrl(data.propData.lat, data.propData.lon);
|
||||
}, [data?.propData]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<LoadingPage />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!data || !data.propData) {
|
||||
return <p>{t.contatto.error}</p>;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="font-semibold">{t.contatto.propietario}:</h3>
|
||||
<p>
|
||||
{t.contatto.nome}: {data.propData.locatore}
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<a
|
||||
className="flex items-center gap-2"
|
||||
href={`tel:${data.propData.numero}`}
|
||||
>
|
||||
<Phone />:
|
||||
<span className="text-blue-600 underline">
|
||||
{data.propData.numero}
|
||||
</span>
|
||||
</a>
|
||||
<Link
|
||||
href={`https://wa.me/${data.propData.numero}`}
|
||||
target="_blank"
|
||||
>
|
||||
<Button
|
||||
className="flex items-center gap-2"
|
||||
size="sm"
|
||||
variant="success"
|
||||
>
|
||||
<WhatsAppIcon2 className="size-4 fill-white" />
|
||||
WhatsApp
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<h3 className="font-semibold">{t.contatto.immobile}:</h3>
|
||||
<p>
|
||||
{t.contatto.indirizzo}: {data.propData.indirizzo},{" "}
|
||||
{data.propData.civico} {data.propData.comune} (
|
||||
{data.propData.provincia})
|
||||
</p>
|
||||
<a
|
||||
className="flex w-fit items-center gap-2 rounded-md border border-border px-3 py-2"
|
||||
href={navigationUrl}
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Navigation /> <span>{t.contatto.apri_nav}</span>
|
||||
</a>
|
||||
<br />
|
||||
<p>
|
||||
{t.contatto.sbloccato_il}:{" "}
|
||||
{data.opened_at.toLocaleString("it", {
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
})}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 rounded-md bg-red-500 p-2 text-white">
|
||||
<TriangleAlert className="size-8" />
|
||||
<div>
|
||||
<p>{t.contatto.contatta}</p>
|
||||
<p className="font-semibold">{t.contatto.orari}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
<CredenzaClose asChild>
|
||||
<Button aria-label="Chiudi modal credenza" variant="outline">
|
||||
{t.chiudi}
|
||||
</Button>
|
||||
</CredenzaClose>
|
||||
</CredenzaFooter>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -104,8 +104,12 @@ export const AnnunciSelezionati = ({
|
|||
<DialogServizio
|
||||
title="Annunci selezionati"
|
||||
trigger={
|
||||
<div className="relative">
|
||||
<Button className="text-lg" size="xl" variant="orange">
|
||||
<div className="relative flex w-full sm:w-fit">
|
||||
<Button
|
||||
className="w-full text-lg sm:w-fit"
|
||||
size="xl"
|
||||
variant="orange"
|
||||
>
|
||||
Annunci selezionati
|
||||
</Button>
|
||||
{annunci.length > 0 && (
|
||||
|
|
@ -159,8 +163,8 @@ export const AnnunciCompatibili = () => {
|
|||
<DialogServizio
|
||||
title="Annunci compatibili"
|
||||
trigger={
|
||||
<div className="relative">
|
||||
<Button className="bg-purple-500 text-lg" size="xl">
|
||||
<div className="relative flex w-full sm:w-fit">
|
||||
<Button className="w-full bg-purple-500 text-lg sm:w-fit" size="xl">
|
||||
Annunci compatibili
|
||||
</Button>
|
||||
{data && data.length > 0 && (
|
||||
|
|
|
|||
|
|
@ -108,14 +108,7 @@ const Row = ({
|
|||
</div>
|
||||
|
||||
<CollapsibleContent className="mt-2 overflow-hidden transition-all duration-300 ease-in-out data-[state=closed]:animate-collapsible-up data-[state=open]:animate-collapsible-down">
|
||||
<ServizioProvider
|
||||
isAdmin={isAdmin}
|
||||
servizio={servizio}
|
||||
servizioId={servizio.servizio_id}
|
||||
userId={userId}
|
||||
>
|
||||
<ServizioContent />
|
||||
</ServizioProvider>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</ServizioProvider>
|
||||
|
|
|
|||
|
|
@ -25,10 +25,16 @@ import { Button } from "~/components/ui/button";
|
|||
import { uploadHandler } from "~/hooks/storageClienSideHooks";
|
||||
import { useTranslation } from "~/providers/I18nProvider";
|
||||
import type { UsersId } from "~/schemas/public/Users";
|
||||
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
export type UploadCallback = (args: {
|
||||
storageIds: string[];
|
||||
userStorageIds: UsersStorageUserStorageId[];
|
||||
}) => void;
|
||||
|
||||
type UploadModalProps = {
|
||||
cb_onUpload?: () => void;
|
||||
cb_onUpload?: UploadCallback;
|
||||
isAdmin?: boolean;
|
||||
userId?: UsersId;
|
||||
};
|
||||
|
|
@ -69,7 +75,7 @@ export const UploadModal = ({
|
|||
};
|
||||
|
||||
type UploadComponentProps = {
|
||||
cb_onUpload?: () => void;
|
||||
cb_onUpload?: UploadCallback;
|
||||
isAdmin?: boolean;
|
||||
userId?: UsersId;
|
||||
maxMB?: number;
|
||||
|
|
@ -90,7 +96,10 @@ export const UploadComponent = ({
|
|||
const { mutateAsync: setUserStorage } =
|
||||
api.storage.addUserStorageEntry.useMutation({
|
||||
onMutate: () => {
|
||||
toast(t.allegati.allegato_caricato, { icon: "📤" });
|
||||
toast(t.allegati.allegato_caricato, {
|
||||
icon: "📤",
|
||||
toasterId: "uploading-toast",
|
||||
});
|
||||
},
|
||||
onSettled: async () => {
|
||||
await utils.storage.getAll.invalidate();
|
||||
|
|
@ -160,6 +169,8 @@ export const UploadComponent = ({
|
|||
if (files.length === 0) {
|
||||
return;
|
||||
}
|
||||
let storageIds: string[] = [];
|
||||
const userStorageIds: UsersStorageUserStorageId[] = [];
|
||||
try {
|
||||
const from_admin = isAdmin ?? true;
|
||||
setIsUploading(true);
|
||||
|
|
@ -168,14 +179,24 @@ export const UploadComponent = ({
|
|||
files,
|
||||
from_admin,
|
||||
});
|
||||
if (!status) {
|
||||
toast.error(t.allegati.errore_caricamento, {
|
||||
icon: "❌",
|
||||
toasterId: "uploading-toast",
|
||||
});
|
||||
setIsUploading(false);
|
||||
return;
|
||||
}
|
||||
storageIds = attachments;
|
||||
|
||||
if (userId) {
|
||||
for (const storageId of attachments) {
|
||||
await setUserStorage({
|
||||
const res = await setUserStorage({
|
||||
storageId,
|
||||
userId,
|
||||
from_admin,
|
||||
});
|
||||
userStorageIds.push(res.user_storage_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,16 +214,9 @@ export const UploadComponent = ({
|
|||
}, randomDelay / 1); // Adjust interval based on random delay
|
||||
});
|
||||
|
||||
if (!status) {
|
||||
toast.error(t.allegati.errore_caricamento, {
|
||||
icon: "❌",
|
||||
});
|
||||
setIsUploading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success(t.allegati.success_caricamento, {
|
||||
icon: "✅",
|
||||
toasterId: "uploading-toast",
|
||||
});
|
||||
} catch (error) {
|
||||
setIsUploading(false);
|
||||
|
|
@ -211,11 +225,12 @@ export const UploadComponent = ({
|
|||
error instanceof Error
|
||||
? error.message
|
||||
: t.allegati.errore_sconosciuto,
|
||||
{ icon: "❌", toasterId: "uploading-toast" },
|
||||
);
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
setFiles([]);
|
||||
cb_onUpload?.();
|
||||
cb_onUpload?.({ storageIds, userStorageIds });
|
||||
await utils.storage.getAll.invalidate();
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import type { UsersId } from './Users';
|
||||
import type { default as TipologiaPosizioneEnum } from './TipologiaPosizioneEnum';
|
||||
import type { UsersStorageUserStorageId } from './UsersStorage';
|
||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||
|
||||
/** Identifier type for public.servizio */
|
||||
|
|
@ -59,6 +60,12 @@ export default interface ServizioTable {
|
|||
isOkSaldo: ColumnType<boolean, boolean | undefined, boolean>;
|
||||
|
||||
isOkConsulenza: ColumnType<boolean, boolean | undefined, boolean>;
|
||||
|
||||
doc_personale_fronte_ref: ColumnType<UsersStorageUserStorageId | null, UsersStorageUserStorageId | null, UsersStorageUserStorageId | null>;
|
||||
|
||||
doc_personale_retro_ref: ColumnType<UsersStorageUserStorageId | null, UsersStorageUserStorageId | null, UsersStorageUserStorageId | null>;
|
||||
|
||||
doc_motivazione_ref: ColumnType<UsersStorageUserStorageId | null, UsersStorageUserStorageId | null, UsersStorageUserStorageId | null>;
|
||||
}
|
||||
|
||||
export type Servizio = Selectable<ServizioTable>;
|
||||
|
|
|
|||
|
|
@ -4,8 +4,13 @@
|
|||
import type { UsersId } from './Users';
|
||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||
|
||||
/** Identifier type for public.users_storage */
|
||||
export type UsersStorageUserStorageId = string & { __brand: 'public.users_storage' };
|
||||
|
||||
/** Represents the table public.users_storage */
|
||||
export default interface UsersStorageTable {
|
||||
user_storage_id: ColumnType<UsersStorageUserStorageId, UsersStorageUserStorageId | undefined, UsersStorageUserStorageId>;
|
||||
|
||||
userId: ColumnType<UsersId, UsersId, UsersId>;
|
||||
|
||||
storageId: ColumnType<string, string, string>;
|
||||
|
|
|
|||
|
|
@ -627,6 +627,45 @@ export const getAllServizioAnnunci = async (userId: UsersId) => {
|
|||
"servizio.isOkAcconto",
|
||||
"servizio.isOkSaldo",
|
||||
"servizio.isOkConsulenza",
|
||||
jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom("users_storage")
|
||||
.select([
|
||||
"users_storage.storageId as storageId",
|
||||
"servizio.doc_personale_fronte_ref as ref",
|
||||
])
|
||||
.whereRef(
|
||||
"users_storage.user_storage_id",
|
||||
"=",
|
||||
"servizio.doc_personale_fronte_ref",
|
||||
),
|
||||
).as("doc_personale_fronte"),
|
||||
jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom("users_storage")
|
||||
.select([
|
||||
"users_storage.storageId as storageId",
|
||||
"servizio.doc_personale_retro_ref as ref",
|
||||
])
|
||||
.whereRef(
|
||||
"users_storage.user_storage_id",
|
||||
"=",
|
||||
"servizio.doc_personale_retro_ref",
|
||||
),
|
||||
).as("doc_personale_retro"),
|
||||
jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom("users_storage")
|
||||
.select([
|
||||
"users_storage.storageId as storageId",
|
||||
"servizio.doc_motivazione_ref as ref",
|
||||
])
|
||||
.whereRef(
|
||||
"users_storage.user_storage_id",
|
||||
"=",
|
||||
"servizio.doc_motivazione_ref",
|
||||
),
|
||||
).as("doc_motivazione"),
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("servizio_annunci")
|
||||
|
|
|
|||
|
|
@ -114,8 +114,8 @@ export const addUserStorage = async (data: NewUsersStorage) => {
|
|||
.insertInto("users_storage")
|
||||
.values(data)
|
||||
.onConflict((oc) => oc.columns(["userId", "storageId"]).doNothing())
|
||||
.returningAll()
|
||||
.execute();
|
||||
.returning("user_storage_id")
|
||||
.executeTakeFirstOrThrow();
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
|
|
|
|||
|
|
@ -18,29 +18,12 @@ services:
|
|||
interval: 1s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
# minio:
|
||||
# image: minio/minio
|
||||
# expose:
|
||||
# - "9000"
|
||||
# - "9001"
|
||||
# volumes:
|
||||
# - minio_storage:/data
|
||||
# networks:
|
||||
# - dokploy-network
|
||||
# environment:
|
||||
# MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
# MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
# command: server --console-address ":9001" /data
|
||||
# healthcheck:
|
||||
# test: [ "CMD", "curl", "-f", "http://minio:9000/minio/health/live" ]
|
||||
# interval: 10s
|
||||
# timeout: 10s
|
||||
# retries: 5
|
||||
storage:
|
||||
image: "ghcr.io/marcopedone/go_fs:latest"
|
||||
ports:
|
||||
- "8080:8080"
|
||||
expose:
|
||||
- "8080"
|
||||
restart: unless-stopped
|
||||
pull_policy: "always"
|
||||
environment:
|
||||
PORT: 8080
|
||||
AUTH_TOKEN: ${STORAGE_TOKEN}
|
||||
|
|
@ -106,10 +89,8 @@ services:
|
|||
- images_volume:/app/images
|
||||
- storage_volume:/app/storage
|
||||
environment:
|
||||
STORAGE_STRATEGY: "s3"
|
||||
MINIO_URL: minio:9000
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
STORAGE_URL: http://storage:8080
|
||||
STORAGE_TOKEN: ${STORAGE_TOKEN}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue