feat: implement image and video reconciliation, update pricing options, and enhance media removal functionality

This commit is contained in:
Marco Pedone 2026-03-02 15:35:23 +01:00
parent 74320248e0
commit c31a68ef6d
15 changed files with 263 additions and 70 deletions

View file

@ -305,7 +305,7 @@ func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) {
return nil, err return nil, err
} }
dbImagesMap := make(map[string][]imgRef) // Convert slice to map for faster lookup dbImagesMap := make(map[string][]imgRef)
for _, ref := range db_images { for _, ref := range db_images {
dbImagesMap[ref.Codice] = append(dbImagesMap[ref.Codice], imgRef{ dbImagesMap[ref.Codice] = append(dbImagesMap[ref.Codice], imgRef{
Og_url: ref.Og_url, Og_url: ref.Og_url,
@ -318,7 +318,8 @@ func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) {
for _, annuncio := range annunci.Annuncio { for _, annuncio := range annunci.Annuncio {
if annuncio.Codice != nil && annuncio.Foto != nil { if annuncio.Codice != nil && annuncio.Foto != nil {
refFoto, existsInDb := dbImagesMap[*annuncio.Codice] // Check if codice exists in DB
refFotos, existsInDb := dbImagesMap[*annuncio.Codice]
if !existsInDb { if !existsInDb {
codiciToProcess = append(codiciToProcess, *annuncio.Codice) codiciToProcess = append(codiciToProcess, *annuncio.Codice)
continue continue
@ -326,8 +327,10 @@ func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) {
needProcess := false needProcess := false
for _, fotoUrl := range *annuncio.Foto { for _, fotoUrl := range *annuncio.Foto {
found := false found := false
for _, ref := range refFoto { // Check if the fotoUrl exists in the DB refs
for _, ref := range refFotos {
if fotoUrl == ref.Og_url { if fotoUrl == ref.Og_url {
// check if the corresponding img and thumb are in the bucket
if bucketIdsMap[ref.Img] && bucketIdsMap[ref.Thumb] { if bucketIdsMap[ref.Img] && bucketIdsMap[ref.Thumb] {
found = true found = true
break break
@ -374,3 +377,42 @@ func ForceClearImages(codici []string) error {
} }
return nil return nil
} }
// Removes stranded images (in storage but not in DB)
func ReconcileImages() error {
currentBucketState, err := ListBucket("images")
if err != nil {
return fmt.Errorf("failed to list bucket: %w", err)
}
//make a list of bucket ids
bucketIdsMap := make(map[string]bool)
for _, item := range currentBucketState {
bucketIdsMap[item.ID] = true
}
db_images, err := queries.GetImagesFromDB()
if err != nil {
return fmt.Errorf("failed to get images from DB: %w", err)
}
knownStorageIds := make(map[string]bool)
for _, ref := range db_images {
knownStorageIds[ref.Img] = true
knownStorageIds[ref.Thumb] = true
}
// reconcile: delete storage files not referenced in DB
var strandedIds []string
for _, item := range currentBucketState {
if !knownStorageIds[item.ID] {
strandedIds = append(strandedIds, item.ID)
}
}
if len(strandedIds) > 0 {
log.Printf("Reconciliation: found %d stranded files, deleting...", len(strandedIds))
if err := DeleteIdsFromStorage(strandedIds); err != nil {
log.Printf("Warning: failed to delete stranded files: %v", err) // non-fatal
}
} else {
log.Println("Reconciliation: no stranded files found.")
}
return nil
}

View file

@ -204,6 +204,7 @@ func (m *MiogestHandler) ParseAnnunci(codFilter string, imgPool *[]typesdefs.Med
image_codes_to_process = append(image_codes_to_process, codFilter) image_codes_to_process = append(image_codes_to_process, codFilter)
} }
} }
err = ReconcileImages()
err = ForceClearImages(image_codes_to_process) err = ForceClearImages(image_codes_to_process)
if err != nil { if err != nil {
log.Fatalf("Failed to clear images for codice %s: %v", codFilter, err.Error()) log.Fatalf("Failed to clear images for codice %s: %v", codFilter, err.Error())
@ -237,6 +238,7 @@ func (m *MiogestHandler) ParseAnnunci(codFilter string, imgPool *[]typesdefs.Med
video_codes_to_process = append(video_codes_to_process, codFilter) video_codes_to_process = append(video_codes_to_process, codFilter)
} }
} }
err = ReconcileVideos()
err = ForceClearVideos(video_codes_to_process) err = ForceClearVideos(video_codes_to_process)
if err != nil { if err != nil {
log.Fatalf("Failed to clear videos for codice %s: %v", codFilter, err.Error()) log.Fatalf("Failed to clear videos for codice %s: %v", codFilter, err.Error())

View file

@ -201,8 +201,8 @@ type AnnuncioXML struct {
} `xml:"Clienti"` } `xml:"Clienti"`
Cap *string `xml:"Cap"` Cap *string `xml:"Cap"`
Video *[]string `xml:"Video"` Video *[]string `xml:"Video"`
AMMedias *[]struct { AMMedias *struct {
AMVideo *struct { AMVideo []struct {
Url *string `xml:"Url"` Url *string `xml:"Url"`
} `xml:"AMVideo"` } `xml:"AMVideo"`
} `xml:"AMMedias"` } `xml:"AMMedias"`

View file

@ -27,10 +27,10 @@ func AddToVideoProcessing(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.Ann
miogestVideos := []string{} miogestVideos := []string{}
if annuncio.AMMedias != nil { if annuncio.AMMedias != nil {
for _, media := range *annuncio.AMMedias { for _, media := range annuncio.AMMedias.AMVideo {
if media.AMVideo != nil && media.AMVideo.Url != nil { if media.Url != nil {
if strings.Contains(*media.AMVideo.Url, "video.miogest") { if strings.Contains(*media.Url, "video.miogest") {
miogestVideos = append(miogestVideos, *media.AMVideo.Url) miogestVideos = append(miogestVideos, *media.Url)
} }
} }
@ -347,12 +347,12 @@ func CodiciToProcessVideos(annunci *typesdefs.AnnunciXML) ([]string, error) {
var codiciToProcess []string var codiciToProcess []string
for _, annuncio := range annunci.Annuncio { for _, annuncio := range annunci.Annuncio {
if annuncio.Codice != nil && annuncio.AMMedias != nil && len(*annuncio.AMMedias) > 0 { if annuncio.Codice != nil && annuncio.AMMedias != nil {
mediaUrls := []string{} mediaUrls := []string{}
for _, media := range *annuncio.AMMedias { for _, media := range annuncio.AMMedias.AMVideo {
if media.AMVideo != nil && media.AMVideo.Url != nil { if media.Url != nil {
if strings.Contains(*media.AMVideo.Url, "video.miogest") { if strings.Contains(*media.Url, "video.miogest") {
mediaUrls = append(mediaUrls, *media.AMVideo.Url) mediaUrls = append(mediaUrls, *media.Url)
} }
} }
} }
@ -367,17 +367,17 @@ func CodiciToProcessVideos(annunci *typesdefs.AnnunciXML) ([]string, error) {
} }
needProcess := false needProcess := false
for _, media := range *annuncio.AMMedias { for _, media := range annuncio.AMMedias.AMVideo {
if media.AMVideo == nil || media.AMVideo.Url == nil { if media.Url == nil {
continue continue
} }
if !strings.Contains(*media.AMVideo.Url, "video.miogest") { if !strings.Contains(*media.Url, "video.miogest") {
continue continue
} }
found := false found := false
for _, ref := range refVideos { for _, ref := range refVideos {
if *media.AMVideo.Url == ref.Og_url { if *media.Url == ref.Og_url {
if bucketIdsMap[ref.Video] && bucketIdsMap[ref.Thumb] { if bucketIdsMap[ref.Video] && bucketIdsMap[ref.Thumb] {
found = true found = true
break break
@ -425,3 +425,36 @@ func ForceClearVideos(codici []string) error {
} }
return nil return nil
} }
// ReconcileVideos removes stranded videos (in storage but not in DB)
func ReconcileVideos() error {
currentBucketState, err := ListBucket("videos")
if err != nil {
return fmt.Errorf("failed to list bucket: %w", err)
}
//make a list of bucket ids
bucketIdsMap := make(map[string]bool)
for _, item := range currentBucketState {
bucketIdsMap[item.ID] = true
}
db_videos, err := queries.GetVideosFromDB()
if err != nil {
return fmt.Errorf("failed to get videos from DB: %w", err)
}
var strandedIds []string
for _, ref := range db_videos {
if !bucketIdsMap[ref.Video] {
strandedIds = append(strandedIds, ref.Video)
}
if !bucketIdsMap[ref.Thumb] {
strandedIds = append(strandedIds, ref.Thumb)
}
}
if len(strandedIds) > 0 {
err := DeleteIdsFromStorage(strandedIds)
if err != nil {
return fmt.Errorf("failed to delete stranded videos from storage: %w", err)
}
}
return nil
}

View file

@ -33,7 +33,6 @@ export const PricingChoice = () => {
downPayment: prezziario.stabile.acconto.prezzo_cent / 100, downPayment: prezziario.stabile.acconto.prezzo_cent / 100,
secondPayment: s.prezzo_cent / 100, secondPayment: s.prezzo_cent / 100,
})); }));
return ( return (
<div className="flex flex-col items-center justify-center gap-4"> <div className="flex flex-col items-center justify-center gap-4">
<h2 className="font-semibold text-3xl">Scegli il tuo servizio</h2> <h2 className="font-semibold text-3xl">Scegli il tuo servizio</h2>
@ -75,6 +74,7 @@ export const PricingChoice = () => {
opzioni={[ opzioni={[
t.pricing_cmp.stabile_option1, t.pricing_cmp.stabile_option1,
t.pricing_cmp.stabile_option2, t.pricing_cmp.stabile_option2,
t.pricing_cmp.stabile_option3,
]} ]}
pricingData={pricingStabile} pricingData={pricingStabile}
tipo={TipologiaPosizioneEnum.Stabile} tipo={TipologiaPosizioneEnum.Stabile}
@ -103,6 +103,7 @@ export const PricingComponent = ({
className, className,
title, title,
tipo, tipo,
optionsBtnClassName,
}: { }: {
pricingData: PricingData; pricingData: PricingData;
opzioni: string[]; opzioni: string[];
@ -111,6 +112,7 @@ export const PricingComponent = ({
statico?: boolean; statico?: boolean;
className?: string; className?: string;
tipo: TipologiaPosizioneEnum; tipo: TipologiaPosizioneEnum;
optionsBtnClassName?: string;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [Value, setValue] = useState<number>(0); const [Value, setValue] = useState<number>(0);
@ -213,6 +215,7 @@ export const PricingComponent = ({
className={cn( className={cn(
"h-9 xs:h-10 rounded-md border-none px-3 xs:px-4 xs:py-2", "h-9 xs:h-10 rounded-md border-none px-3 xs:px-4 xs:py-2",
idx === Value ? "fade-in-0 animate-in" : "", idx === Value ? "fade-in-0 animate-in" : "",
optionsBtnClassName,
)} )}
key={opzione} key={opzione}
onClick={() => { onClick={() => {

View file

@ -8,11 +8,12 @@ import {
RefreshCcw, RefreshCcw,
RotateCw, RotateCw,
TriangleAlert, TriangleAlert,
X,
} from "lucide-react"; } from "lucide-react";
import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import z from "zod"; import z from "zod";
import { CarouselAnnuncio } from "~/components/annuncio_card";
import { Confirm } from "~/components/confirm"; import { Confirm } from "~/components/confirm";
import { import {
Form, Form,
@ -41,8 +42,10 @@ import {
} from "~/components/ui/popover"; } from "~/components/ui/popover";
import { Switch } from "~/components/ui/switch"; import { Switch } from "~/components/ui/switch";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
import { VideoPlayer } from "~/components/videoPlayer";
import { filteredCaratteristiche } from "~/hooks/schedaAnnuncioUtils"; import { filteredCaratteristiche } from "~/hooks/schedaAnnuncioUtils";
import { NullableStringOnChange } from "~/lib/form_utils"; import { NullableStringOnChange } from "~/lib/form_utils";
import { getStorageUrl } from "~/lib/storage_utils";
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils";
import { useZodForm } from "~/lib/zodForm"; import { useZodForm } from "~/lib/zodForm";
import { StatusBadge } from "~/pages/area-riservata/admin/edit-annuncio/[id]"; import { StatusBadge } from "~/pages/area-riservata/admin/edit-annuncio/[id]";
@ -109,14 +112,6 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => {
titolo_it: z.string().nullable(), titolo_it: z.string().nullable(),
unita_condominio: z.number().nullable(), unita_condominio: z.number().nullable(),
web: z.boolean().nullable(), web: z.boolean().nullable(),
// MEDIA
//url_immagini: z.string().array().nullable(),
//url_video: z.string().array().nullable(),
//og_url: z.string().nullable(),
//creato_il: z.date().nullable(),
//modificato_il: z.date().nullable(),
}); });
type FormValues = z.infer<typeof schema>; type FormValues = z.infer<typeof schema>;
@ -160,6 +155,14 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => {
}, },
}); });
const { mutateAsync: removeMedia } =
api.annunci.removeAnnuncioMedia.useMutation({
onSettled: async () => {
await revalidate({ cod: data.codice });
toast.success("Media rimosso con successo");
},
});
const defaultValues: FormValues = { const defaultValues: FormValues = {
...data, ...data,
mq: Number(data.mq) || null, mq: Number(data.mq) || null,
@ -535,19 +538,6 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => {
/> />
</CardContent> </CardContent>
</Card> </Card>
<Card className="overflow-hidden">
<CardHeader>
<CardTitle>Imagini Annuncio</CardTitle>
</CardHeader>
<CardContent>
<CarouselAnnuncio
immagini={data.images}
single
updated_at={data.media_updated_at}
videos={data.videos}
/>
</CardContent>
</Card>
</div> </div>
</div> </div>
<Card> <Card>
@ -1843,7 +1833,80 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => {
<CardTitle>Media</CardTitle> <CardTitle>Media</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="flex w-full flex-col items-start justify-between gap-4 sm:flex-row"></div> <div className="flex w-full flex-wrap justify-between gap-4">
{data.images.map((img) => (
<div className="relative" key={img.img}>
<Image
alt={img.img}
className="aspect-square size-52 rounded-md bg-[#e6e9ec] object-cover"
height={400}
key={img.img}
src={getStorageUrl({
storageId: img.img,
params: {
cacheKey: data.media_updated_at?.toISOString(),
media: "image",
},
})}
width={800}
/>
<Confirm
description="Sei sicuro di voler rimuovere questo media? Questa azione è irreversibile fino al prossimo aggiornamento."
onConfirm={async () =>
removeMedia({ type: "img", id: img.img })
}
title="Rimuovi media"
>
<Button
className="absolute top-2 right-2"
size="sm"
type="button"
variant="destructive"
>
<X className="size-4" />
</Button>
</Confirm>
</div>
))}
{data.videos.map((video) => (
<div className="relative" key={video.video}>
<VideoPlayer
className="aspect-square size-52 object-cover"
coverImage={getStorageUrl({
storageId: video.thumb,
params: {
cacheKey: data.media_updated_at?.toISOString(),
media: "image",
},
})}
key={`videoplayer-${video.video}`}
videoSrc={getStorageUrl({
storageId: video.video,
params: {
cacheKey: data.media_updated_at?.toISOString(),
media: "video",
},
})}
/>
<Confirm
description="Sei sicuro di voler rimuovere questo media? Questa azione è irreversibile fino al prossimo aggiornamento."
onConfirm={async () =>
removeMedia({ type: "video", id: video.video })
}
title="Rimuovi media"
>
<Button
className="absolute top-2 right-2"
size="sm"
type="button"
variant="destructive"
>
<X className="size-4" />
</Button>
</Confirm>
</div>
))}
</div>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>

View file

@ -133,12 +133,7 @@ export const FormPrezzo = ({
<FormMessage /> <FormMessage />
</div> </div>
<FormControl> <FormControl>
<Input <Input placeholder="" {...field} id="idprezziario" />
placeholder=""
{...field}
disabled={initialValues != null}
id="idprezziario"
/>
</FormControl> </FormControl>
</FormItem> </FormItem>
)} )}

View file

@ -1187,7 +1187,8 @@ The Stable Rent service, ideal for those with demonstrable work references and f
stabile_footer: stabile_footer:
"Types of stable rent: Libero (4+4 years), Subsidized (3+2 years) or commercial (6+6 years)", "Types of stable rent: Libero (4+4 years), Subsidized (3+2 years) or commercial (6+6 years)",
stabile_option1: "Up to 500 €/month", stabile_option1: "Up to 500 €/month",
stabile_option2: "Over 500 €/month", stabile_option2: "Up to 700 €/month",
stabile_option3: "Over 700 €/month",
stabile_select: "Select the rent:", stabile_select: "Select the rent:",
titolo: "Pay based on your search", titolo: "Pay based on your search",
txt_tipo_acquisto: "For the purchase of any type of property", txt_tipo_acquisto: "For the purchase of any type of property",

View file

@ -1184,7 +1184,8 @@ export const it: LangDict = {
stabile_footer: stabile_footer:
"Tipologie di affitto stabile: libero (4+4 anni), agevolato (3+2 anni) o commerciale (6+6 anni)", "Tipologie di affitto stabile: libero (4+4 anni), agevolato (3+2 anni) o commerciale (6+6 anni)",
stabile_option1: "Fino a 500€/mese", stabile_option1: "Fino a 500€/mese",
stabile_option2: "Superiore a 500 €/mese", stabile_option2: "Fino a 700€/mese",
stabile_option3: "Oltre 700€/mese",
stabile_select: "Seleziona il canone:", stabile_select: "Seleziona il canone:",
titolo: "Paga in base alla tua ricerca", titolo: "Paga in base alla tua ricerca",
txt_tipo_acquisto: "Per l'acquisto di qualsiasi tipologia di immobile", txt_tipo_acquisto: "Per l'acquisto di qualsiasi tipologia di immobile",

View file

@ -185,6 +185,7 @@ export type LangDict = {
stabile_select: string; stabile_select: string;
stabile_option1: string; stabile_option1: string;
stabile_option2: string; stabile_option2: string;
stabile_option3: string;
stabile_footer: string; stabile_footer: string;
breve_select: string; breve_select: string;
breve_option1: string; breve_option1: string;

View file

@ -307,7 +307,11 @@ const Pricing = ({
t.pricing_cmp.breve_option4, t.pricing_cmp.breve_option4,
t.pricing_cmp.breve_option5, t.pricing_cmp.breve_option5,
] ]
: [t.pricing_cmp.stabile_option1, t.pricing_cmp.stabile_option2]; : [
t.pricing_cmp.stabile_option1,
t.pricing_cmp.stabile_option2,
t.pricing_cmp.stabile_option3,
];
const style = const style =
tipologia === TipologiaPosizioneEnum.Transitorio tipologia === TipologiaPosizioneEnum.Transitorio
@ -456,7 +460,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
<CardContent className="flex flex-col gap-4 p-4 py-0"> <CardContent className="flex flex-col gap-4 p-4 py-0">
<div className="grid grid-cols-2 gap-3 font-semibold"> <div className="grid grid-cols-2 gap-3 font-semibold">
<div className="relative"> <div className="relative">
<div className="rounded-md bg-primary-foreground p-2 text-center text-primary"> <div className="flex h-10 items-center justify-center rounded-md bg-primary-foreground text-center text-primary">
{data.prezzo && (data.prezzo / 1e2).toFixed(2).replace(".", ",")}{" "} {data.prezzo && (data.prezzo / 1e2).toFixed(2).replace(".", ",")}{" "}
{t.annunci.euro_mese} {t.annunci.euro_mese}
</div> </div>
@ -480,7 +484,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
<div className="relative"> <div className="relative">
<div <div
className={cn( className={cn(
"rounded-md bg-primary-foreground p-2 text-center text-primary outline-2", "h-10 rounded-md bg-primary-foreground p-2 text-center text-primary outline-2",
data.tipo === "Transitorio" && "outline-transitorio", data.tipo === "Transitorio" && "outline-transitorio",
data.tipo === "Stabile" && "outline-stabile", data.tipo === "Stabile" && "outline-stabile",
)} )}
@ -501,6 +505,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary"> <div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
<BedDouble /> <BedDouble />
<span className="line-clamp-1 break-all">
{(() => { {(() => {
if (!data.numero_camere) return "N/A"; if (!data.numero_camere) return "N/A";
if (data.numero_camere > 1) if (data.numero_camere > 1)
@ -508,20 +513,23 @@ const CardInfos = ({ data }: { data: Annunci }) => {
if (data.numero_camere === 1) if (data.numero_camere === 1)
return `${data.numero_camere} ${t.card.camere1}`; return `${data.numero_camere} ${t.card.camere1}`;
})()} })()}
</span>
</div> </div>
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary"> <div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
<Bath /> <Bath />
<span> <span className="line-clamp-1 break-all">
{data.numero_bagni || 0} {t.annunci.bagni} {data.numero_bagni || 0} {t.annunci.bagni}
</span> </span>
</div> </div>
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary"> <div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
<Armchair /> <Armchair />
<span className="line-clamp-1 break-all">
{data.caratteristiche?.Scheda_Arredi || ""} {data.caratteristiche?.Scheda_Arredi || ""}
</span>
</div> </div>
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary"> <div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
<Car /> <Car />
<span> <span className="line-clamp-1 break-all">
{data.numero_postiauto || 0} {t.annunci.posti_auto} {data.numero_postiauto || 0} {t.annunci.posti_auto}
</span> </span>
</div> </div>
@ -532,7 +540,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary"> <div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
<Ruler /> <Ruler />
<span> <span className="line-clamp-1 break-all">
{data.mq ? Number(data.mq).toLocaleString("it-IT") : "--"} m {data.mq ? Number(data.mq).toLocaleString("it-IT") : "--"} m
<sup>2</sup> <sup>2</sup>
</span> </span>
@ -542,7 +550,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
{data.piano !== null && data.piano !== "" && ( {data.piano !== null && data.piano !== "" && (
<> <>
<ArrowUp01 /> <ArrowUp01 />
<span> <span className="line-clamp-1 break-all">
{(() => { {(() => {
if (data.piano === "0") return t.annunci.p_terra; if (data.piano === "0") return t.annunci.p_terra;
if (data.piano === "0.5") return t.annunci.p_rialzato; if (data.piano === "0.5") return t.annunci.p_rialzato;

View file

@ -102,6 +102,7 @@ const Prezzi: NextPage = () => {
opzioni={[ opzioni={[
t.pricing_cmp.stabile_option1, t.pricing_cmp.stabile_option1,
t.pricing_cmp.stabile_option2, t.pricing_cmp.stabile_option2,
t.pricing_cmp.stabile_option3,
]} ]}
pricingData={pricingStabile} pricingData={pricingStabile}
statico statico

View file

@ -19,6 +19,9 @@ import {
getOnline_AnnuncioIdCodice_AnnunciHandler, getOnline_AnnuncioIdCodice_AnnunciHandler,
getOptions_AnnunciHandler, getOptions_AnnunciHandler,
getProprietarioDataHandler, getProprietarioDataHandler,
type ImgToRemove,
removeAnnuncioMedia,
type VideoToRemove,
} from "~/server/controllers/annunci.controller"; } from "~/server/controllers/annunci.controller";
import { db } from "~/server/db"; import { db } from "~/server/db";
import { NewMail } from "~/server/services/mailer"; import { NewMail } from "~/server/services/mailer";
@ -142,4 +145,11 @@ export const annunciRouter = createTRPCRouter({
} }
return { status: "ok", timestamp: Date.now() }; return { status: "ok", timestamp: Date.now() };
}), }),
removeAnnuncioMedia: adminProcedure
.input(z.custom<ImgToRemove | VideoToRemove>())
.mutation(async ({ input }) => {
return await removeAnnuncioMedia({
...input,
});
}),
}); });

View file

@ -495,3 +495,30 @@ export const getProprietarioDataHandler = async ({
}); });
} }
}; };
export type ImgToRemove = {
type: "img";
id: ImagesRefs["img"];
};
export type VideoToRemove = {
type: "video";
id: VideosRefs["video"];
};
export const removeAnnuncioMedia = async ({
id,
type,
}: ImgToRemove | VideoToRemove) => {
try {
if (type === "img") {
await db.deleteFrom("images_refs").where("img", "=", id).execute();
}
if (type === "video") {
await db.deleteFrom("videos_refs").where("video", "=", id).execute();
}
} catch (e) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Errore rimozione media: ${(e as Error).message}`,
});
}
};

View file

@ -461,7 +461,11 @@ export const setupSaldoServizio = async ({
} }
saldo = await qry.where("idprezziario", "=", mpp).executeTakeFirst(); saldo = await qry.where("idprezziario", "=", mpp).executeTakeFirst();
} else if (servizio.tipologia === TipologiaPosizioneEnum.Stabile) { } else if (servizio.tipologia === TipologiaPosizioneEnum.Stabile) {
if (servizio.budget >= 500) { if (servizio.budget >= 700) {
saldo = await qry
.where("idprezziario", "=", "SALDOCA700+" as PrezziarioIdprezziario)
.executeTakeFirst();
} else if (servizio.budget >= 500) {
saldo = await qry saldo = await qry
.where("idprezziario", "=", "SALDOCA500+" as PrezziarioIdprezziario) .where("idprezziario", "=", "SALDOCA500+" as PrezziarioIdprezziario)
@ -588,7 +592,9 @@ export const getPacksPerServizio = async (
} }
case TipologiaPosizioneEnum.Stabile: { case TipologiaPosizioneEnum.Stabile: {
const pack = await getPrezziarioByIdHandler({ const pack = await getPrezziarioByIdHandler({
id: (servizio.budget >= 500 id: (servizio.budget >= 700
? "SALDOCA700+"
: servizio.budget >= 500
? "SALDOCA500+" ? "SALDOCA500+"
: "SALDOCA500") as PrezziarioIdprezziario, : "SALDOCA500") as PrezziarioIdprezziario,
}); });