feat: implement image and video reconciliation, update pricing options, and enhance media removal functionality
This commit is contained in:
parent
74320248e0
commit
c31a68ef6d
15 changed files with 263 additions and 70 deletions
|
|
@ -305,7 +305,7 @@ func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) {
|
|||
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 {
|
||||
dbImagesMap[ref.Codice] = append(dbImagesMap[ref.Codice], imgRef{
|
||||
Og_url: ref.Og_url,
|
||||
|
|
@ -318,7 +318,8 @@ func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) {
|
|||
for _, annuncio := range annunci.Annuncio {
|
||||
|
||||
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 {
|
||||
codiciToProcess = append(codiciToProcess, *annuncio.Codice)
|
||||
continue
|
||||
|
|
@ -326,8 +327,10 @@ func CodiciToProcessImages(annunci *typesdefs.AnnunciXML) ([]string, error) {
|
|||
needProcess := false
|
||||
for _, fotoUrl := range *annuncio.Foto {
|
||||
found := false
|
||||
for _, ref := range refFoto {
|
||||
// Check if the fotoUrl exists in the DB refs
|
||||
for _, ref := range refFotos {
|
||||
if fotoUrl == ref.Og_url {
|
||||
// check if the corresponding img and thumb are in the bucket
|
||||
if bucketIdsMap[ref.Img] && bucketIdsMap[ref.Thumb] {
|
||||
found = true
|
||||
break
|
||||
|
|
@ -374,3 +377,42 @@ func ForceClearImages(codici []string) error {
|
|||
}
|
||||
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
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ func (m *MiogestHandler) ParseAnnunci(codFilter string, imgPool *[]typesdefs.Med
|
|||
image_codes_to_process = append(image_codes_to_process, codFilter)
|
||||
}
|
||||
}
|
||||
err = ReconcileImages()
|
||||
err = ForceClearImages(image_codes_to_process)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
err = ReconcileVideos()
|
||||
err = ForceClearVideos(video_codes_to_process)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to clear videos for codice %s: %v", codFilter, err.Error())
|
||||
|
|
|
|||
|
|
@ -201,8 +201,8 @@ type AnnuncioXML struct {
|
|||
} `xml:"Clienti"`
|
||||
Cap *string `xml:"Cap"`
|
||||
Video *[]string `xml:"Video"`
|
||||
AMMedias *[]struct {
|
||||
AMVideo *struct {
|
||||
AMMedias *struct {
|
||||
AMVideo []struct {
|
||||
Url *string `xml:"Url"`
|
||||
} `xml:"AMVideo"`
|
||||
} `xml:"AMMedias"`
|
||||
|
|
|
|||
|
|
@ -27,10 +27,10 @@ func AddToVideoProcessing(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.Ann
|
|||
|
||||
miogestVideos := []string{}
|
||||
if annuncio.AMMedias != nil {
|
||||
for _, media := range *annuncio.AMMedias {
|
||||
if media.AMVideo != nil && media.AMVideo.Url != nil {
|
||||
if strings.Contains(*media.AMVideo.Url, "video.miogest") {
|
||||
miogestVideos = append(miogestVideos, *media.AMVideo.Url)
|
||||
for _, media := range annuncio.AMMedias.AMVideo {
|
||||
if media.Url != nil {
|
||||
if strings.Contains(*media.Url, "video.miogest") {
|
||||
miogestVideos = append(miogestVideos, *media.Url)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -347,12 +347,12 @@ func CodiciToProcessVideos(annunci *typesdefs.AnnunciXML) ([]string, error) {
|
|||
var codiciToProcess []string
|
||||
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{}
|
||||
for _, media := range *annuncio.AMMedias {
|
||||
if media.AMVideo != nil && media.AMVideo.Url != nil {
|
||||
if strings.Contains(*media.AMVideo.Url, "video.miogest") {
|
||||
mediaUrls = append(mediaUrls, *media.AMVideo.Url)
|
||||
for _, media := range annuncio.AMMedias.AMVideo {
|
||||
if media.Url != nil {
|
||||
if strings.Contains(*media.Url, "video.miogest") {
|
||||
mediaUrls = append(mediaUrls, *media.Url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -367,17 +367,17 @@ func CodiciToProcessVideos(annunci *typesdefs.AnnunciXML) ([]string, error) {
|
|||
}
|
||||
needProcess := false
|
||||
|
||||
for _, media := range *annuncio.AMMedias {
|
||||
if media.AMVideo == nil || media.AMVideo.Url == nil {
|
||||
for _, media := range annuncio.AMMedias.AMVideo {
|
||||
if media.Url == nil {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(*media.AMVideo.Url, "video.miogest") {
|
||||
if !strings.Contains(*media.Url, "video.miogest") {
|
||||
continue
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, ref := range refVideos {
|
||||
if *media.AMVideo.Url == ref.Og_url {
|
||||
if *media.Url == ref.Og_url {
|
||||
if bucketIdsMap[ref.Video] && bucketIdsMap[ref.Thumb] {
|
||||
found = true
|
||||
break
|
||||
|
|
@ -425,3 +425,36 @@ func ForceClearVideos(codici []string) error {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ export const PricingChoice = () => {
|
|||
downPayment: prezziario.stabile.acconto.prezzo_cent / 100,
|
||||
secondPayment: s.prezzo_cent / 100,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-4">
|
||||
<h2 className="font-semibold text-3xl">Scegli il tuo servizio</h2>
|
||||
|
|
@ -75,6 +74,7 @@ export const PricingChoice = () => {
|
|||
opzioni={[
|
||||
t.pricing_cmp.stabile_option1,
|
||||
t.pricing_cmp.stabile_option2,
|
||||
t.pricing_cmp.stabile_option3,
|
||||
]}
|
||||
pricingData={pricingStabile}
|
||||
tipo={TipologiaPosizioneEnum.Stabile}
|
||||
|
|
@ -103,6 +103,7 @@ export const PricingComponent = ({
|
|||
className,
|
||||
title,
|
||||
tipo,
|
||||
optionsBtnClassName,
|
||||
}: {
|
||||
pricingData: PricingData;
|
||||
opzioni: string[];
|
||||
|
|
@ -111,6 +112,7 @@ export const PricingComponent = ({
|
|||
statico?: boolean;
|
||||
className?: string;
|
||||
tipo: TipologiaPosizioneEnum;
|
||||
optionsBtnClassName?: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [Value, setValue] = useState<number>(0);
|
||||
|
|
@ -213,6 +215,7 @@ export const PricingComponent = ({
|
|||
className={cn(
|
||||
"h-9 xs:h-10 rounded-md border-none px-3 xs:px-4 xs:py-2",
|
||||
idx === Value ? "fade-in-0 animate-in" : "",
|
||||
optionsBtnClassName,
|
||||
)}
|
||||
key={opzione}
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@ import {
|
|||
RefreshCcw,
|
||||
RotateCw,
|
||||
TriangleAlert,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import toast from "react-hot-toast";
|
||||
import z from "zod";
|
||||
import { CarouselAnnuncio } from "~/components/annuncio_card";
|
||||
import { Confirm } from "~/components/confirm";
|
||||
import {
|
||||
Form,
|
||||
|
|
@ -41,8 +42,10 @@ import {
|
|||
} from "~/components/ui/popover";
|
||||
import { Switch } from "~/components/ui/switch";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
|
||||
import { VideoPlayer } from "~/components/videoPlayer";
|
||||
import { filteredCaratteristiche } from "~/hooks/schedaAnnuncioUtils";
|
||||
import { NullableStringOnChange } from "~/lib/form_utils";
|
||||
import { getStorageUrl } from "~/lib/storage_utils";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { useZodForm } from "~/lib/zodForm";
|
||||
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(),
|
||||
unita_condominio: z.number().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>;
|
||||
|
|
@ -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 = {
|
||||
...data,
|
||||
mq: Number(data.mq) || null,
|
||||
|
|
@ -535,19 +538,6 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => {
|
|||
/>
|
||||
</CardContent>
|
||||
</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>
|
||||
<Card>
|
||||
|
|
@ -1843,7 +1833,80 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => {
|
|||
<CardTitle>Media</CardTitle>
|
||||
</CardHeader>
|
||||
<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>
|
||||
</Card>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -133,12 +133,7 @@ export const FormPrezzo = ({
|
|||
<FormMessage />
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder=""
|
||||
{...field}
|
||||
disabled={initialValues != null}
|
||||
id="idprezziario"
|
||||
/>
|
||||
<Input placeholder="" {...field} id="idprezziario" />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1187,7 +1187,8 @@ The Stable Rent service, ideal for those with demonstrable work references and f
|
|||
stabile_footer:
|
||||
"Types of stable rent: Libero (4+4 years), Subsidized (3+2 years) or commercial (6+6 years)",
|
||||
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:",
|
||||
titolo: "Pay based on your search",
|
||||
txt_tipo_acquisto: "For the purchase of any type of property",
|
||||
|
|
|
|||
|
|
@ -1183,8 +1183,9 @@ export const it: LangDict = {
|
|||
saldi_stabile: ["Fino a 500 €/mese", "Superiore a 500 €/mese"],
|
||||
stabile_footer:
|
||||
"Tipologie di affitto stabile: libero (4+4 anni), agevolato (3+2 anni) o commerciale (6+6 anni)",
|
||||
stabile_option1: "Fino a 500 €/mese",
|
||||
stabile_option2: "Superiore a 500 €/mese",
|
||||
stabile_option1: "Fino a 500€/mese",
|
||||
stabile_option2: "Fino a 700€/mese",
|
||||
stabile_option3: "Oltre 700€/mese",
|
||||
stabile_select: "Seleziona il canone:",
|
||||
titolo: "Paga in base alla tua ricerca",
|
||||
txt_tipo_acquisto: "Per l'acquisto di qualsiasi tipologia di immobile",
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ export type LangDict = {
|
|||
stabile_select: string;
|
||||
stabile_option1: string;
|
||||
stabile_option2: string;
|
||||
stabile_option3: string;
|
||||
stabile_footer: string;
|
||||
breve_select: string;
|
||||
breve_option1: string;
|
||||
|
|
|
|||
|
|
@ -307,7 +307,11 @@ const Pricing = ({
|
|||
t.pricing_cmp.breve_option4,
|
||||
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 =
|
||||
tipologia === TipologiaPosizioneEnum.Transitorio
|
||||
|
|
@ -456,7 +460,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
|
|||
<CardContent className="flex flex-col gap-4 p-4 py-0">
|
||||
<div className="grid grid-cols-2 gap-3 font-semibold">
|
||||
<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(".", ",")}{" "}
|
||||
{t.annunci.euro_mese}
|
||||
</div>
|
||||
|
|
@ -480,7 +484,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
|
|||
<div className="relative">
|
||||
<div
|
||||
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 === "Stabile" && "outline-stabile",
|
||||
)}
|
||||
|
|
@ -501,27 +505,31 @@ const CardInfos = ({ data }: { data: Annunci }) => {
|
|||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
|
||||
<BedDouble />
|
||||
{(() => {
|
||||
if (!data.numero_camere) return "N/A";
|
||||
if (data.numero_camere > 1)
|
||||
return `${data.numero_camere} ${t.card.camere2}`;
|
||||
if (data.numero_camere === 1)
|
||||
return `${data.numero_camere} ${t.card.camere1}`;
|
||||
})()}
|
||||
<span className="line-clamp-1 break-all">
|
||||
{(() => {
|
||||
if (!data.numero_camere) return "N/A";
|
||||
if (data.numero_camere > 1)
|
||||
return `${data.numero_camere} ${t.card.camere2}`;
|
||||
if (data.numero_camere === 1)
|
||||
return `${data.numero_camere} ${t.card.camere1}`;
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
|
||||
<Bath />
|
||||
<span>
|
||||
<span className="line-clamp-1 break-all">
|
||||
{data.numero_bagni || 0} {t.annunci.bagni}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
|
||||
<Armchair />
|
||||
{data.caratteristiche?.Scheda_Arredi || ""}
|
||||
<span className="line-clamp-1 break-all">
|
||||
{data.caratteristiche?.Scheda_Arredi || ""}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
|
||||
<Car />
|
||||
<span>
|
||||
<span className="line-clamp-1 break-all">
|
||||
{data.numero_postiauto || 0} {t.annunci.posti_auto}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -532,7 +540,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
|
|||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
|
||||
<Ruler />
|
||||
<span>
|
||||
<span className="line-clamp-1 break-all">
|
||||
{data.mq ? Number(data.mq).toLocaleString("it-IT") : "--"} m
|
||||
<sup>2</sup>
|
||||
</span>
|
||||
|
|
@ -542,7 +550,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
|
|||
{data.piano !== null && data.piano !== "" && (
|
||||
<>
|
||||
<ArrowUp01 />
|
||||
<span>
|
||||
<span className="line-clamp-1 break-all">
|
||||
{(() => {
|
||||
if (data.piano === "0") return t.annunci.p_terra;
|
||||
if (data.piano === "0.5") return t.annunci.p_rialzato;
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ const Prezzi: NextPage = () => {
|
|||
opzioni={[
|
||||
t.pricing_cmp.stabile_option1,
|
||||
t.pricing_cmp.stabile_option2,
|
||||
t.pricing_cmp.stabile_option3,
|
||||
]}
|
||||
pricingData={pricingStabile}
|
||||
statico
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ import {
|
|||
getOnline_AnnuncioIdCodice_AnnunciHandler,
|
||||
getOptions_AnnunciHandler,
|
||||
getProprietarioDataHandler,
|
||||
type ImgToRemove,
|
||||
removeAnnuncioMedia,
|
||||
type VideoToRemove,
|
||||
} from "~/server/controllers/annunci.controller";
|
||||
import { db } from "~/server/db";
|
||||
import { NewMail } from "~/server/services/mailer";
|
||||
|
|
@ -142,4 +145,11 @@ export const annunciRouter = createTRPCRouter({
|
|||
}
|
||||
return { status: "ok", timestamp: Date.now() };
|
||||
}),
|
||||
removeAnnuncioMedia: adminProcedure
|
||||
.input(z.custom<ImgToRemove | VideoToRemove>())
|
||||
.mutation(async ({ input }) => {
|
||||
return await removeAnnuncioMedia({
|
||||
...input,
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -461,7 +461,11 @@ export const setupSaldoServizio = async ({
|
|||
}
|
||||
saldo = await qry.where("idprezziario", "=", mpp).executeTakeFirst();
|
||||
} 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
|
||||
.where("idprezziario", "=", "SALDOCA500+" as PrezziarioIdprezziario)
|
||||
|
||||
|
|
@ -588,9 +592,11 @@ export const getPacksPerServizio = async (
|
|||
}
|
||||
case TipologiaPosizioneEnum.Stabile: {
|
||||
const pack = await getPrezziarioByIdHandler({
|
||||
id: (servizio.budget >= 500
|
||||
? "SALDOCA500+"
|
||||
: "SALDOCA500") as PrezziarioIdprezziario,
|
||||
id: (servizio.budget >= 700
|
||||
? "SALDOCA700+"
|
||||
: servizio.budget >= 500
|
||||
? "SALDOCA500+"
|
||||
: "SALDOCA500") as PrezziarioIdprezziario,
|
||||
});
|
||||
if (!pack) {
|
||||
return { success: false, message: "Pack non trovato" };
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue