feat: implement Potenziali and PotenzialiGroups management with CRUD operations and UI enhancements
This commit is contained in:
parent
3143870cfd
commit
cfcddea9c8
11 changed files with 673 additions and 142 deletions
37
apps/db/migrations/28_potenziali.up.sql
Normal file
37
apps/db/migrations/28_potenziali.up.sql
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
-- Potenziali table
|
||||
CREATE TABLE IF NOT EXISTS public.potenziali (
|
||||
id UUID DEFAULT gen_random_uuid () NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
descrizione TEXT,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT now() NOT NULL,
|
||||
userid UUID,
|
||||
potenziali_group_id UUID NOT NULL
|
||||
);
|
||||
|
||||
-- Primary Key
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname = 'potenziali_pkey'
|
||||
AND conrelid = 'public.potenziali'::regclass
|
||||
) THEN
|
||||
ALTER TABLE public.potenziali
|
||||
ADD CONSTRAINT potenziali_pkey PRIMARY KEY (id);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Foreign Key to Users
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname = 'potenziali_assigned_user'
|
||||
AND conrelid = 'public.potenziali'::regclass
|
||||
) THEN
|
||||
ALTER TABLE public.potenziali
|
||||
ADD CONSTRAINT potenziali_assigned_user
|
||||
FOREIGN KEY (userid) REFERENCES public.users (id)
|
||||
ON UPDATE CASCADE ON DELETE SET NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
45
apps/db/migrations/29_potenziali_groups.up.sql
Normal file
45
apps/db/migrations/29_potenziali_groups.up.sql
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
-- Potenziali Groups Table
|
||||
CREATE TABLE IF NOT EXISTS public.potenziali_groups (
|
||||
id UUID DEFAULT gen_random_uuid () NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
color TEXT DEFAULT 'blue' NOT NULL
|
||||
);
|
||||
-- Primary Key
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname = 'potenziali_groups_pkey'
|
||||
AND conrelid = 'public.potenziali_groups'::regclass
|
||||
) THEN
|
||||
ALTER TABLE public.potenziali_groups
|
||||
ADD CONSTRAINT potenziali_groups_pkey PRIMARY KEY (id);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Foreign Key to Potenziali Groups
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname = 'potenziali_group_fk'
|
||||
AND conrelid = 'public.potenziali'::regclass
|
||||
) THEN
|
||||
ALTER TABLE public.potenziali
|
||||
ADD CONSTRAINT potenziali_group_fk
|
||||
FOREIGN KEY (potenziali_group_id) REFERENCES public.potenziali_groups (id)
|
||||
ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Create Index on Potenziali Group ID
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_indexes
|
||||
WHERE indexname = 'idx_potenziali_group_id'
|
||||
) THEN
|
||||
CREATE INDEX idx_potenziali_group_id
|
||||
ON public.potenziali (potenziali_group_id);
|
||||
END IF;
|
||||
END $$;
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { format } from "date-fns";
|
||||
import { Plus } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
KanbanBoard,
|
||||
KanbanCard,
|
||||
|
|
@ -8,12 +10,9 @@ import {
|
|||
KanbanHeader,
|
||||
KanbanProvider,
|
||||
} from "~/components/ui/kanban";
|
||||
import {
|
||||
type ElementoPotenziali,
|
||||
PotenzialiProvider,
|
||||
usePotenziali,
|
||||
} from "~/providers/PotenzialiProvider";
|
||||
import type { UsersId } from "~/schemas/public/Users";
|
||||
import { usePotenziali } from "~/providers/PotenzialiProvider";
|
||||
import type { PotenzialiGroupsId } from "~/schemas/public/PotenzialiGroups";
|
||||
import type { PotenzialeData } from "~/server/services/potenziali.service";
|
||||
import {
|
||||
Credenza,
|
||||
CredenzaBody,
|
||||
|
|
@ -21,81 +20,27 @@ import {
|
|||
CredenzaDescription,
|
||||
CredenzaHeader,
|
||||
CredenzaTitle,
|
||||
CredenzaTrigger,
|
||||
} from "../custom_ui/credenza";
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuTrigger,
|
||||
} from "../ui/context-menu";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "../ui/dialog";
|
||||
import Input from "../custom_ui/input";
|
||||
import { Textarea } from "../custom_ui/textarea";
|
||||
import { Button } from "../ui/button";
|
||||
import { Label } from "../ui/label";
|
||||
import { UserAvatar } from "../user_avatar";
|
||||
|
||||
const columns = [
|
||||
{ id: "todo", name: "To Do", color: "red" },
|
||||
{ id: "inprogress", name: "In Progress", color: "blue" },
|
||||
{ id: "done", name: "Done", color: "green" },
|
||||
];
|
||||
|
||||
const SampleData: ElementoPotenziali[] = [
|
||||
{
|
||||
id: "1",
|
||||
name: "Mario Rossi",
|
||||
column: "todo",
|
||||
descrizione:
|
||||
"Single, cerca appartamento in centro da subito, budget 800 euro",
|
||||
createdAt: new Date(),
|
||||
owner: {
|
||||
userId: "019b9a25-eff1-420a-9017-b0add205d7b6" as UsersId,
|
||||
username: "Marco Pedone",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
name: "Luigi Bianchi",
|
||||
column: "inprogress",
|
||||
descrizione: "Coppia, cerca bilocale in periferia, budget 600 euro",
|
||||
createdAt: new Date(),
|
||||
owner: null,
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
name: "Giovanni Verdi",
|
||||
column: "done",
|
||||
descrizione:
|
||||
"Famiglia con 2 figli, cerca trilocale vicino a scuole, budget 1000 euro",
|
||||
createdAt: new Date(),
|
||||
owner: {
|
||||
userId: "019b9a25-eff1-420a-9017-b0add205d7b6" as UsersId,
|
||||
username: "Marco Pedone",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const Potenziali = () => {
|
||||
return (
|
||||
<PotenzialiProvider data={SampleData}>
|
||||
<Kaban />
|
||||
</PotenzialiProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const Kaban = () => {
|
||||
const { data, updateData, setEditingId, editingId } = usePotenziali();
|
||||
|
||||
export const Potenziali = () => {
|
||||
const { data, columns, setData, updateCall, setEditingId, editingId } =
|
||||
usePotenziali();
|
||||
return (
|
||||
<>
|
||||
<KanbanProvider
|
||||
className="w-max"
|
||||
columns={columns}
|
||||
data={data}
|
||||
onDataChange={updateData}
|
||||
onDataChange={setData}
|
||||
onDragEnd={() => {
|
||||
updateCall();
|
||||
}}
|
||||
>
|
||||
{(column) => (
|
||||
<KanbanBoard
|
||||
|
|
@ -110,10 +55,12 @@ const Kaban = () => {
|
|||
style={{ backgroundColor: column.color }}
|
||||
/>
|
||||
<span>{column.name}</span>
|
||||
|
||||
<NewCard columnId={column.id} />
|
||||
</div>
|
||||
</KanbanHeader>
|
||||
<KanbanCards className="w-68" id={column.id}>
|
||||
{(data: ElementoPotenziali) => (
|
||||
{(data: PotenzialeData) => (
|
||||
<KanbanCard
|
||||
cardOnClick={() => {
|
||||
setEditingId(data.id);
|
||||
|
|
@ -132,16 +79,16 @@ const Kaban = () => {
|
|||
{data.descrizione}
|
||||
</p>
|
||||
</div>
|
||||
{data.owner && (
|
||||
{data.userid && (
|
||||
<UserAvatar
|
||||
className="size-6 shrink-0 text-sm"
|
||||
userId={data.owner.userId}
|
||||
username={data.owner.username}
|
||||
userId={data.userid}
|
||||
username={data.username || "Utente"}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-2 text-muted-foreground text-xs">
|
||||
{format(data.createdAt, "dd/MM/yyyy HH:mm")}
|
||||
{format(data.created_at, "dd/MM/yyyy HH:mm")}
|
||||
</p>
|
||||
</KanbanCard>
|
||||
)}
|
||||
|
|
@ -163,6 +110,101 @@ const Kaban = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export const NewColumn = () => {
|
||||
const [newColumnName, setNewColumnName] = useState("");
|
||||
const { addColumn } = usePotenziali();
|
||||
return (
|
||||
<Credenza>
|
||||
<CredenzaTrigger asChild>
|
||||
<Button>Nuova colonna</Button>
|
||||
</CredenzaTrigger>
|
||||
<CredenzaContent className="max-h-[90vh]">
|
||||
<CredenzaHeader>
|
||||
<CredenzaTitle className="text-center font-semibold text-2xl">
|
||||
Nuova colonna
|
||||
</CredenzaTitle>
|
||||
<CredenzaDescription className="sr-only">desc</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody className="flex max-h-[80vh] flex-col gap-4 overflow-auto p-2">
|
||||
<Label htmlFor="titolo">Titolo</Label>
|
||||
|
||||
<Input
|
||||
defaultValue={newColumnName}
|
||||
id="titolo"
|
||||
onChange={(e) => setNewColumnName(e.target.value)}
|
||||
type="string"
|
||||
/>
|
||||
<Button
|
||||
disabled={!newColumnName.length}
|
||||
onClick={() => {
|
||||
addColumn(newColumnName);
|
||||
setNewColumnName("");
|
||||
}}
|
||||
>
|
||||
Aggiungi
|
||||
</Button>
|
||||
</CredenzaBody>
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
);
|
||||
};
|
||||
|
||||
export const NewCard = ({ columnId }: { columnId: PotenzialiGroupsId }) => {
|
||||
const [newItemName, setNewItemName] = useState("");
|
||||
const [newItemDescrizione, setNewItemDescrizione] = useState("");
|
||||
const { addItem } = usePotenziali();
|
||||
return (
|
||||
<Credenza>
|
||||
<CredenzaTrigger asChild>
|
||||
<Button className="ml-auto" size="sm">
|
||||
<Plus className="size-4" />
|
||||
</Button>
|
||||
</CredenzaTrigger>
|
||||
<CredenzaContent className="max-h-[90vh]">
|
||||
<CredenzaHeader>
|
||||
<CredenzaTitle className="text-center font-semibold text-2xl">
|
||||
Nuova colonna
|
||||
</CredenzaTitle>
|
||||
<CredenzaDescription className="sr-only">desc</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody className="flex max-h-[80vh] flex-col gap-4 overflow-auto p-2">
|
||||
<Label htmlFor="titolo">Titolo</Label>
|
||||
|
||||
<Input
|
||||
defaultValue={newItemName}
|
||||
id="titolo"
|
||||
onChange={(e) => setNewItemName(e.target.value)}
|
||||
type="string"
|
||||
/>
|
||||
<Label htmlFor="descrizione">Descrizione</Label>
|
||||
<Textarea
|
||||
className="min-h-64"
|
||||
defaultValue={newItemDescrizione}
|
||||
id="descrizione"
|
||||
onChange={(e) => setNewItemDescrizione(e.target.value)}
|
||||
placeholder=""
|
||||
/>
|
||||
|
||||
<Button
|
||||
disabled={!newItemName.length}
|
||||
onClick={() => {
|
||||
addItem({
|
||||
name: newItemName,
|
||||
descrizione: newItemDescrizione,
|
||||
potenziali_group_id: columnId,
|
||||
});
|
||||
setNewItemName("");
|
||||
setNewItemDescrizione("");
|
||||
}}
|
||||
>
|
||||
Aggiungi
|
||||
</Button>
|
||||
</CredenzaBody>
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
);
|
||||
};
|
||||
|
||||
const EditingContent = () => {
|
||||
const { getEditingItem } = usePotenziali();
|
||||
const item = getEditingItem();
|
||||
|
|
@ -177,11 +219,9 @@ const EditingContent = () => {
|
|||
</CredenzaHeader>
|
||||
<CredenzaBody className="flex max-h-[80vh] flex-col gap-4 overflow-auto pb-5">
|
||||
<p>Descrizione: {item.descrizione}</p>
|
||||
<p>Creato il: {format(item.createdAt, "dd/MM/yyyy HH:mm")}</p>
|
||||
<p>Utente Assegnato: {item.owner ? item.owner.username : "Nessuno"}</p>
|
||||
<p>Creato il: {format(item.created_at, "dd/MM/yyyy HH:mm")}</p>
|
||||
<p>Utente Assegnato: {item.userid ? item.username : "Nessuno"}</p>
|
||||
</CredenzaBody>
|
||||
</CredenzaContent>
|
||||
);
|
||||
};
|
||||
|
||||
export default Potenziali;
|
||||
|
|
|
|||
|
|
@ -1,53 +1,43 @@
|
|||
import dynamic from "next/dynamic";
|
||||
import { NewColumn } from "~/components/area-riservata/potenziali";
|
||||
import { AreaRiservataLayout } from "~/components/Layout";
|
||||
import { LoadingPage } from "~/components/loading";
|
||||
import type { NextPageWithLayout } from "~/pages/_app";
|
||||
import { PotenzialiProvider } from "~/providers/PotenzialiProvider";
|
||||
|
||||
const Potenziali = dynamic(
|
||||
() => import("~/components/area-riservata/potenziali"),
|
||||
const PotenzialiBoard = dynamic(
|
||||
() =>
|
||||
import("~/components/area-riservata/potenziali").then(
|
||||
(mod) => mod.Potenziali,
|
||||
),
|
||||
{
|
||||
loading: () => <LoadingPage />,
|
||||
ssr: false,
|
||||
loading: () => <LoadingPage />,
|
||||
},
|
||||
);
|
||||
|
||||
const AdminPotenziali: NextPageWithLayout = () => {
|
||||
return (
|
||||
<PotenzialiProvider>
|
||||
<div className="mx-1 flex-1 overflow-auto">
|
||||
<div className="mx-auto pt-4">
|
||||
<div className="mx-3 items-start justify-between md:flex">
|
||||
<div className="mx-3 flex-wrap items-start justify-between pb-5 sm:flex">
|
||||
<div className="flex max-w-lg items-center gap-3">
|
||||
<h3 className="font-bold text-accent-foreground text-xl sm:text-2xl">
|
||||
Potenziali
|
||||
</h3>
|
||||
</div>
|
||||
<NewColumn />
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<Potenziali />
|
||||
</div>
|
||||
|
||||
<PotenzialiBoard />
|
||||
</div>
|
||||
</div>
|
||||
</PotenzialiProvider>
|
||||
);
|
||||
};
|
||||
export default AdminPotenziali;
|
||||
|
||||
// export const getServerSideProps = (async (context) => {
|
||||
// const access_token = context.req.cookies.access_token;
|
||||
|
||||
// const helpers = await TrpcAuthedFetchingIstance({ access_token });
|
||||
// if (helpers) {
|
||||
// await helpers.trpc.etichette.getAllEtichette.prefetch();
|
||||
// return {
|
||||
// props: {
|
||||
// trpcState: helpers.trpc.dehydrate(),
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
// return {
|
||||
// props: {},
|
||||
// };
|
||||
// }) satisfies GetServerSideProps;
|
||||
|
||||
AdminPotenziali.getLayout = function getLayout(page) {
|
||||
return <AreaRiservataLayout>{page}</AreaRiservataLayout>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,56 +1,109 @@
|
|||
import { createContext, useContext, useState } from "react";
|
||||
import type { UsersId } from "~/schemas/public/Users";
|
||||
import toast from "react-hot-toast";
|
||||
import type { NewPotenziali, PotenzialiId } from "~/schemas/public/Potenziali";
|
||||
import type { PotenzialiGroups } from "~/schemas/public/PotenzialiGroups";
|
||||
import type { PotenzialeData } from "~/server/services/potenziali.service";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
export type ElementoPotenziali = {
|
||||
id: string;
|
||||
name: string;
|
||||
column: string;
|
||||
descrizione: string;
|
||||
createdAt: Date;
|
||||
owner: { userId: UsersId; username: string } | null;
|
||||
};
|
||||
type PotenzialiContextType = {
|
||||
data: ElementoPotenziali[];
|
||||
editingId: string | null;
|
||||
updateData: (data: ElementoPotenziali[]) => void;
|
||||
setEditingId: (id: string | null) => void;
|
||||
getEditingItem: () => ElementoPotenziali | null;
|
||||
addItem: (item: ElementoPotenziali) => void;
|
||||
removeItem: (id: string) => void;
|
||||
data: PotenzialeData[];
|
||||
columns: PotenzialiGroups[];
|
||||
editingId: PotenzialiId | null;
|
||||
setData: (data: PotenzialeData[]) => void;
|
||||
updateCall: () => void;
|
||||
setEditingId: (id: PotenzialiId | null) => void;
|
||||
getEditingItem: () => PotenzialeData | null;
|
||||
addItem: (item: NewPotenziali) => void;
|
||||
removeItem: (id: PotenzialiId) => void;
|
||||
addColumn: (name: string) => void;
|
||||
};
|
||||
|
||||
const PotenzialiContext = createContext<PotenzialiContextType | null>(null);
|
||||
|
||||
type PotenzialiProviderProps = {
|
||||
children: React.ReactNode;
|
||||
data: ElementoPotenziali[];
|
||||
};
|
||||
export const PotenzialiProvider = ({
|
||||
children,
|
||||
data: initial,
|
||||
}: PotenzialiProviderProps) => {
|
||||
const [data, setData] = useState<ElementoPotenziali[]>(initial);
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
export const PotenzialiProvider = ({ children }: PotenzialiProviderProps) => {
|
||||
const { data } = api.potenziali.getPotenziali.useQuery();
|
||||
|
||||
const { data: columns } = api.potenziali.getPotenzialiGroups.useQuery();
|
||||
|
||||
const [editingId, setEditingId] = useState<PotenzialiId | null>(null);
|
||||
|
||||
const getEditingItem = () => {
|
||||
return data.find((item) => item.id === editingId) || null;
|
||||
return data?.find((item) => item.id === editingId) || null;
|
||||
};
|
||||
const addItem = (item: ElementoPotenziali) => {
|
||||
setData((prev) => [...prev, item]);
|
||||
const utils = api.useUtils();
|
||||
const { mutate: addItemMutation } = api.potenziali.addPotenziale.useMutation({
|
||||
onSuccess: async () => {
|
||||
toast.success("Elemento aggiunto con successo");
|
||||
await utils.potenziali.getPotenziali.invalidate();
|
||||
await utils.potenziali.getPotenzialiGroups.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
const addItem = (item: NewPotenziali) => {
|
||||
addItemMutation({
|
||||
data: item,
|
||||
});
|
||||
};
|
||||
const removeItem = (id: string) => {
|
||||
setData((prev) => prev.filter((item) => item.id !== id));
|
||||
const { mutate: removeItemMutation } =
|
||||
api.potenziali.deletePotenziale.useMutation({
|
||||
onSuccess: async () => {
|
||||
toast.success("Elemento rimosso con successo");
|
||||
await utils.potenziali.getPotenziali.invalidate();
|
||||
await utils.potenziali.getPotenzialiGroups.invalidate();
|
||||
},
|
||||
});
|
||||
const removeItem = (id: PotenzialiId) => {
|
||||
removeItemMutation({
|
||||
potenzialiId: id,
|
||||
});
|
||||
};
|
||||
const setData = (newData: PotenzialeData[]) => {
|
||||
utils.potenziali.getPotenziali.setData(undefined, newData);
|
||||
};
|
||||
|
||||
const { mutate: updatePotenzialiMutation } =
|
||||
api.potenziali.updatePotenziali.useMutation({
|
||||
onSuccess: async () => {
|
||||
await utils.potenziali.getPotenziali.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
const updateCall = () => {
|
||||
updatePotenzialiMutation({
|
||||
data: utils.potenziali.getPotenziali.getData() || [],
|
||||
});
|
||||
};
|
||||
|
||||
const { mutate: addColumnMutation } =
|
||||
api.potenziali.addPotenzialeGroup.useMutation({
|
||||
onSuccess: async () => {
|
||||
toast.success("Colonna aggiunta con successo");
|
||||
await utils.potenziali.getPotenziali.invalidate();
|
||||
await utils.potenziali.getPotenzialiGroups.invalidate();
|
||||
},
|
||||
});
|
||||
const addColumn = (name: string) => {
|
||||
addColumnMutation({
|
||||
name,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<PotenzialiContext.Provider
|
||||
value={{
|
||||
data,
|
||||
data: data || [],
|
||||
columns: columns || [],
|
||||
editingId,
|
||||
updateData: setData,
|
||||
setData,
|
||||
updateCall,
|
||||
setEditingId,
|
||||
getEditingItem,
|
||||
addItem,
|
||||
removeItem,
|
||||
addColumn,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
30
apps/infoalloggi/src/schemas/public/Potenziali.ts
Normal file
30
apps/infoalloggi/src/schemas/public/Potenziali.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// @generated
|
||||
// This file is automatically generated by Kanel. Do not modify manually.
|
||||
|
||||
import type { UsersId } from './Users';
|
||||
import type { PotenzialiGroupsId } from './PotenzialiGroups';
|
||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||
|
||||
/** Identifier type for public.potenziali */
|
||||
export type PotenzialiId = string & { __brand: 'public.potenziali' };
|
||||
|
||||
/** Represents the table public.potenziali */
|
||||
export default interface PotenzialiTable {
|
||||
id: ColumnType<PotenzialiId, PotenzialiId | undefined, PotenzialiId>;
|
||||
|
||||
name: ColumnType<string, string, string>;
|
||||
|
||||
descrizione: ColumnType<string | null, string | null, string | null>;
|
||||
|
||||
created_at: ColumnType<Date, Date | string | undefined, Date | string>;
|
||||
|
||||
userid: ColumnType<UsersId | null, UsersId | null, UsersId | null>;
|
||||
|
||||
potenziali_group_id: ColumnType<PotenzialiGroupsId, PotenzialiGroupsId, PotenzialiGroupsId>;
|
||||
}
|
||||
|
||||
export type Potenziali = Selectable<PotenzialiTable>;
|
||||
|
||||
export type NewPotenziali = Insertable<PotenzialiTable>;
|
||||
|
||||
export type PotenzialiUpdate = Updateable<PotenzialiTable>;
|
||||
22
apps/infoalloggi/src/schemas/public/PotenzialiGroups.ts
Normal file
22
apps/infoalloggi/src/schemas/public/PotenzialiGroups.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// @generated
|
||||
// This file is automatically generated by Kanel. Do not modify manually.
|
||||
|
||||
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||
|
||||
/** Identifier type for public.potenziali_groups */
|
||||
export type PotenzialiGroupsId = string & { __brand: 'public.potenziali_groups' };
|
||||
|
||||
/** Represents the table public.potenziali_groups */
|
||||
export default interface PotenzialiGroupsTable {
|
||||
id: ColumnType<PotenzialiGroupsId, PotenzialiGroupsId | undefined, PotenzialiGroupsId>;
|
||||
|
||||
name: ColumnType<string, string, string>;
|
||||
|
||||
color: ColumnType<string, string | undefined, string>;
|
||||
}
|
||||
|
||||
export type PotenzialiGroups = Selectable<PotenzialiGroupsTable>;
|
||||
|
||||
export type NewPotenzialiGroups = Insertable<PotenzialiGroupsTable>;
|
||||
|
||||
export type PotenzialiGroupsUpdate = Updateable<PotenzialiGroupsTable>;
|
||||
|
|
@ -11,6 +11,7 @@ import type { default as UsersStorageTable } from './UsersStorage';
|
|||
import type { default as ServizioAnnunciTable } from './ServizioAnnunci';
|
||||
import type { default as EmailsTable } from './Emails';
|
||||
import type { default as PaymentsTable } from './Payments';
|
||||
import type { default as PotenzialiGroupsTable } from './PotenzialiGroups';
|
||||
import type { default as ComuniTable } from './Comuni';
|
||||
import type { default as EventQueueTable } from './EventQueue';
|
||||
import type { default as OrdiniTable } from './Ordini';
|
||||
|
|
@ -21,6 +22,7 @@ import type { default as EtichetteTable } from './Etichette';
|
|||
import type { default as MessagesTable } from './Messages';
|
||||
import type { default as ProvincieTable } from './Provincie';
|
||||
import type { default as UsersAnagraficaTable } from './UsersAnagrafica';
|
||||
import type { default as PotenzialiTable } from './Potenziali';
|
||||
import type { default as VideosRefsTable } from './VideosRefs';
|
||||
import type { default as BanlistTable } from './Banlist';
|
||||
import type { default as NazioniTable } from './Nazioni';
|
||||
|
|
@ -49,6 +51,8 @@ export default interface PublicSchema {
|
|||
|
||||
payments: PaymentsTable;
|
||||
|
||||
potenziali_groups: PotenzialiGroupsTable;
|
||||
|
||||
comuni: ComuniTable;
|
||||
|
||||
event_queue: EventQueueTable;
|
||||
|
|
@ -69,6 +73,8 @@ export default interface PublicSchema {
|
|||
|
||||
users_anagrafica: UsersAnagraficaTable;
|
||||
|
||||
potenziali: PotenzialiTable;
|
||||
|
||||
videos_refs: VideosRefsTable;
|
||||
|
||||
banlist: BanlistTable;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { bannersRouter } from "./routers/banners";
|
|||
import { catastoRouter } from "./routers/catasto";
|
||||
import { etichetteRouter } from "./routers/etichette";
|
||||
import { flagsRouter } from "./routers/flags";
|
||||
import { potenzialiRouter } from "./routers/potenziali";
|
||||
import { revalidationRouter } from "./routers/revalidation";
|
||||
import { stringsRouter } from "./routers/strings";
|
||||
import { syncRouter } from "./routers/sync";
|
||||
|
|
@ -55,5 +56,6 @@ export const appRouter = createTRPCRouter({
|
|||
etichette: etichetteRouter,
|
||||
strings: stringsRouter,
|
||||
revalidation: revalidationRouter,
|
||||
potenziali: potenzialiRouter,
|
||||
});
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
|
|
|||
121
apps/infoalloggi/src/server/api/routers/potenziali.ts
Normal file
121
apps/infoalloggi/src/server/api/routers/potenziali.ts
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import z from "zod";
|
||||
import type {
|
||||
NewPotenziali,
|
||||
PotenzialiId,
|
||||
PotenzialiUpdate,
|
||||
} from "~/schemas/public/Potenziali";
|
||||
import type { PotenzialiGroupsId } from "~/schemas/public/PotenzialiGroups";
|
||||
import { adminProcedure, createTRPCRouter } from "~/server/api/trpc";
|
||||
import {
|
||||
addPotenziale,
|
||||
addPotenzialeGroup,
|
||||
deletePotenziale,
|
||||
deletePotenzialeGroup,
|
||||
getPotenziali,
|
||||
getPotenzialiGroups,
|
||||
type PotenzialeData,
|
||||
updatePotenziale,
|
||||
updatePotenzialeGroup,
|
||||
updatePotenziali,
|
||||
} from "~/server/services/potenziali.service";
|
||||
|
||||
export const zPotenzialiId = z.custom<PotenzialiId>(
|
||||
(val) => typeof val === "string" && val.length > 0,
|
||||
{ message: "Invalid PotenzialiId" },
|
||||
);
|
||||
|
||||
export const zNewPotenziali = z.custom<NewPotenziali>(
|
||||
(val) => {
|
||||
return typeof val === "object" && val !== null;
|
||||
},
|
||||
{ message: "Invalid NewPotenziali" },
|
||||
);
|
||||
|
||||
export const zPotenzialiUpdate = z.custom<PotenzialiUpdate>(
|
||||
(val) => {
|
||||
return typeof val === "object" && val !== null;
|
||||
},
|
||||
{ message: "Invalid PotenzialiUpdate" },
|
||||
);
|
||||
|
||||
export const zPotenzialiGroupId = z.custom<PotenzialiGroupsId>(
|
||||
(val) => typeof val === "string" && val.length > 0,
|
||||
{ message: "Invalid PotenzialiGroupsId" },
|
||||
);
|
||||
|
||||
export const potenzialiRouter = createTRPCRouter({
|
||||
getPotenziali: adminProcedure.query(async () => {
|
||||
return await getPotenziali();
|
||||
}),
|
||||
getPotenzialiGroups: adminProcedure.query(async () => {
|
||||
return await getPotenzialiGroups();
|
||||
}),
|
||||
addPotenziale: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
data: zNewPotenziali,
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
return await addPotenziale(input.data);
|
||||
}),
|
||||
updatePotenziali: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
data: z.custom<PotenzialeData[]>(),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
return await updatePotenziali(input.data);
|
||||
}),
|
||||
updatePotenziale: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
potenzialiId: zPotenzialiId,
|
||||
data: zPotenzialiUpdate,
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
return await updatePotenziale(input.potenzialiId, input.data);
|
||||
}),
|
||||
deletePotenziale: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
potenzialiId: zPotenzialiId,
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
return await deletePotenziale(input.potenzialiId);
|
||||
}),
|
||||
|
||||
addPotenzialeGroup: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string().min(1).max(255),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
return await addPotenzialeGroup({ name: input.name });
|
||||
}),
|
||||
updatePotenzialeGroup: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
potenzialiGroupId: zPotenzialiGroupId,
|
||||
name: z.string().min(1).max(255),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
return await updatePotenzialeGroup(input.potenzialiGroupId, {
|
||||
name: input.name,
|
||||
});
|
||||
}),
|
||||
deletePotenzialeGroup: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
potenzialiGroupId: zPotenzialiGroupId,
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
return await deletePotenzialeGroup(input.potenzialiGroupId);
|
||||
}),
|
||||
});
|
||||
185
apps/infoalloggi/src/server/services/potenziali.service.ts
Normal file
185
apps/infoalloggi/src/server/services/potenziali.service.ts
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
import { TRPCError } from "@trpc/server";
|
||||
import type { Nullable } from "kysely";
|
||||
import type {
|
||||
NewPotenziali,
|
||||
Potenziali,
|
||||
PotenzialiId,
|
||||
PotenzialiUpdate,
|
||||
} from "~/schemas/public/Potenziali";
|
||||
import type {
|
||||
NewPotenzialiGroups,
|
||||
PotenzialiGroups,
|
||||
PotenzialiGroupsId,
|
||||
PotenzialiGroupsUpdate,
|
||||
} from "~/schemas/public/PotenzialiGroups";
|
||||
import type { Users } from "~/schemas/public/Users";
|
||||
import { db } from "../db";
|
||||
|
||||
export type PotenzialeData = Omit<Potenziali, "potenziali_group_id"> & {
|
||||
column: string;
|
||||
} & Nullable<Pick<Users, "username">>;
|
||||
|
||||
export const getPotenziali = async (): Promise<PotenzialeData[]> => {
|
||||
try {
|
||||
const potenziali = await db
|
||||
.selectFrom("potenziali")
|
||||
.select([
|
||||
"potenziali.id",
|
||||
"potenziali.name",
|
||||
"potenziali.created_at",
|
||||
"potenziali.descrizione",
|
||||
"potenziali.userid",
|
||||
"potenziali.potenziali_group_id as column",
|
||||
])
|
||||
.innerJoin(
|
||||
"potenziali_groups",
|
||||
"potenziali.potenziali_group_id",
|
||||
"potenziali_groups.id",
|
||||
)
|
||||
.leftJoin("users", "potenziali.userid", "users.id")
|
||||
.select("users.username")
|
||||
.orderBy("potenziali_groups.name", "asc")
|
||||
.orderBy("potenziali.created_at", "asc")
|
||||
.execute();
|
||||
|
||||
return potenziali.map((data) => ({
|
||||
...data,
|
||||
created_at: new Date(data.created_at),
|
||||
}));
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: `Failed to fetch potenziali, error: ${(e as Error).message}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const getPotenzialiGroups = async (): Promise<PotenzialiGroups[]> => {
|
||||
try {
|
||||
return await db.selectFrom("potenziali_groups").selectAll().execute();
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: `Failed to fetch potenziali groups, error: ${(e as Error).message}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const addPotenzialeGroup = async (data: NewPotenzialiGroups) => {
|
||||
try {
|
||||
await db.insertInto("potenziali_groups").values(data).execute();
|
||||
return { status: "success" };
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: `Failed to add potenziale group, error: ${(e as Error).message}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const deletePotenzialeGroup = async (id: PotenzialiGroupsId) => {
|
||||
try {
|
||||
await db.deleteFrom("potenziali_groups").where("id", "=", id).execute();
|
||||
return { status: "success" };
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: `Failed to delete potenziale group, error: ${(e as Error).message}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const updatePotenzialeGroup = async (
|
||||
id: PotenzialiGroupsId,
|
||||
data: PotenzialiGroupsUpdate,
|
||||
) => {
|
||||
try {
|
||||
await db
|
||||
.updateTable("potenziali_groups")
|
||||
.set(data)
|
||||
.where("id", "=", id)
|
||||
.execute();
|
||||
return { status: "success" };
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: `Failed to update potenziale group, error: ${(e as Error).message}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const addPotenziale = async (data: NewPotenziali) => {
|
||||
try {
|
||||
await db.insertInto("potenziali").values(data).execute();
|
||||
return { status: "success" };
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: `Failed to add potenziale, error: ${(e as Error).message}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const deletePotenziale = async (id: PotenzialiId) => {
|
||||
try {
|
||||
await db.deleteFrom("potenziali").where("id", "=", id).execute();
|
||||
return { status: "success" };
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: `Failed to delete potenziale, error: ${(e as Error).message}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const updatePotenziale = async (
|
||||
id: PotenzialiId,
|
||||
data: PotenzialiUpdate,
|
||||
) => {
|
||||
try {
|
||||
await db.updateTable("potenziali").set(data).where("id", "=", id).execute();
|
||||
return { status: "success" };
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: `Failed to update potenziale, error: ${(e as Error).message}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const updatePotenziali = async (data: PotenzialeData[]) => {
|
||||
try {
|
||||
const db_mappings = await db
|
||||
.selectFrom("potenziali")
|
||||
.select(["id", "potenziali_group_id"])
|
||||
.execute();
|
||||
|
||||
const updates = data
|
||||
.map((item) => {
|
||||
const db_item = db_mappings.find((db_item) => db_item.id === item.id);
|
||||
if (db_item && db_item.potenziali_group_id !== item.column) {
|
||||
const { column, username: _username, ...rest } = item;
|
||||
return {
|
||||
...rest,
|
||||
potenziali_group_id: column as PotenzialiGroupsId,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((item) => item !== null);
|
||||
|
||||
for (const item of updates) {
|
||||
await db
|
||||
.updateTable("potenziali")
|
||||
.set(item)
|
||||
.where("id", "=", item.id)
|
||||
.execute();
|
||||
}
|
||||
return { status: "success" };
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: `Failed to update potenziali, error: ${(e as Error).message}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue