refactor: remove Potenziali component and related context, update translations

This commit is contained in:
Marco Pedone 2025-11-20 15:06:00 +01:00
parent e7e397759a
commit a9df30cb70
6 changed files with 0 additions and 646 deletions

View file

@ -1,179 +0,0 @@
"use client";
import { format } from "date-fns";
import {
KanbanBoard,
KanbanCard,
KanbanCards,
KanbanHeader,
KanbanProvider,
} from "~/components/ui/kanban";
import {
type ElementoPotenziali,
PotenzialiProvider,
usePotenziali,
} from "~/providers/PotenzialiProvider";
import type { UsersId } from "~/schemas/public/Users";
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
} from "../ui/context-menu";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "../ui/dialog";
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, removeItem } =
usePotenziali();
return (
<>
<KanbanProvider columns={columns} data={data} onDataChange={updateData}>
{(column) => (
<KanbanBoard id={column.id} key={column.id}>
<KanbanHeader>
<div className="flex items-center gap-2">
<div
className="h-2 w-2 rounded-full"
style={{ backgroundColor: column.color }}
/>
<span>{column.name}</span>
</div>
</KanbanHeader>
<KanbanCards id={column.id}>
{(data: ElementoPotenziali) => (
<ContextMenu key={data.id}>
<ContextMenuTrigger>
<KanbanCard
column={column.id}
id={data.id}
name={data.name}
>
<div className="flex items-start justify-between gap-2">
<div className="flex flex-col gap-1">
<p className="m-0 flex-1 font-medium text-sm">
{data.name}
</p>
</div>
{data.owner && (
<UserAvatar
className="size-6 shrink-0"
userId={data.owner.userId}
username={data.owner.username}
/>
)}
</div>
<p className="m-0 text-muted-foreground text-xs">
{format(data.createdAt, "dd/MM/yyyy HH:mm")}
</p>
</KanbanCard>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem
onClick={() => {
setEditingId(data.id);
}}
>
Modifica
</ContextMenuItem>
<ContextMenuItem onClick={() => removeItem(data.id)}>
Elimina
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
)}
</KanbanCards>
</KanbanBoard>
)}
</KanbanProvider>
<Dialog
onOpenChange={(v) => {
if (!v) {
setEditingId(null);
}
}}
open={!!editingId}
>
<EditingContent />
</Dialog>
</>
);
};
const EditingContent = () => {
const { getEditingItem } = usePotenziali();
const item = getEditingItem();
if (!item) return null;
return (
<DialogContent>
<DialogHeader>
<DialogTitle>Modifica {item.name}</DialogTitle>
<DialogDescription className="sr-only">desc</DialogDescription>
</DialogHeader>
<div className="mt-4 flex flex-col gap-4">
<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>
</div>
</DialogContent>
);
};
export default Potenziali;

View file

@ -1,346 +0,0 @@
"use client";
import type {
Announcements,
DndContextProps,
DragEndEvent,
DragOverEvent,
DragStartEvent,
} from "@dnd-kit/core";
import {
closestCenter,
DndContext,
DragOverlay,
KeyboardSensor,
MouseSensor,
TouchSensor,
useDroppable,
useSensor,
useSensors,
} from "@dnd-kit/core";
import { arrayMove, SortableContext, useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import {
createContext,
type HTMLAttributes,
type ReactNode,
useContext,
useState,
} from "react";
import { createPortal } from "react-dom";
import tunnel from "tunnel-rat";
import { Card } from "~/components/ui/card";
import { ScrollArea, ScrollBar } from "~/components/ui/scroll-area";
import { cn } from "~/lib/utils";
//https://www.shadcn.io/components/data/kanban#installation
const t = tunnel();
export type { DragEndEvent } from "@dnd-kit/core";
type KanbanItemProps = {
id: string;
name: string;
column: string;
} & Record<string, unknown>;
type KanbanColumnProps = {
id: string;
name: string;
} & Record<string, unknown>;
type KanbanContextProps<
T extends KanbanItemProps = KanbanItemProps,
C extends KanbanColumnProps = KanbanColumnProps,
> = {
columns: C[];
data: T[];
activeCardId: string | null;
};
const KanbanContext = createContext<KanbanContextProps>({
columns: [],
data: [],
activeCardId: null,
});
export type KanbanBoardProps = {
id: string;
children: ReactNode;
className?: string;
};
export const KanbanBoard = ({ id, children, className }: KanbanBoardProps) => {
const { isOver, setNodeRef } = useDroppable({
id,
});
return (
<div
className={cn(
"flex size-full min-h-40 flex-col divide-y overflow-hidden rounded-md border bg-secondary text-xs shadow-sm ring-2 transition-all",
isOver ? "ring-primary" : "ring-transparent",
className,
)}
ref={setNodeRef}
>
{children}
</div>
);
};
export type KanbanCardProps<T extends KanbanItemProps = KanbanItemProps> = T & {
children?: ReactNode;
className?: string;
};
export const KanbanCard = <T extends KanbanItemProps = KanbanItemProps>({
id,
name,
children,
className,
}: KanbanCardProps<T>) => {
const {
attributes,
listeners,
setNodeRef,
transition,
transform,
isDragging,
} = useSortable({
id,
});
const { activeCardId } = useContext(KanbanContext) as KanbanContextProps;
const style = {
transition,
transform: CSS.Transform.toString(transform),
};
return (
<>
<div style={style} {...listeners} {...attributes} ref={setNodeRef}>
<Card
className={cn(
"cursor-grab gap-4 rounded-md p-3 shadow-sm",
isDragging && "pointer-events-none cursor-grabbing opacity-30",
className,
)}
>
{children ?? <p className="m-0 font-medium text-sm">{name}</p>}
</Card>
</div>
{activeCardId === id && (
<t.In>
<Card
className={cn(
"cursor-grab gap-4 rounded-md p-3 shadow-sm ring-2 ring-primary",
isDragging && "cursor-grabbing",
className,
)}
>
{children ?? <p className="m-0 font-medium text-sm">{name}</p>}
</Card>
</t.In>
)}
</>
);
};
export type KanbanCardsProps<T extends KanbanItemProps = KanbanItemProps> =
Omit<HTMLAttributes<HTMLDivElement>, "children" | "id"> & {
children: (item: T) => ReactNode;
id: string;
};
export const KanbanCards = <T extends KanbanItemProps = KanbanItemProps>({
children,
className,
...props
}: KanbanCardsProps<T>) => {
const { data } = useContext(KanbanContext) as KanbanContextProps<T>;
const filteredData = data.filter((item) => item.column === props.id);
const items = filteredData.map((item) => item.id);
return (
<ScrollArea className="overflow-hidden">
<SortableContext items={items}>
<div
className={cn("flex flex-grow flex-col gap-2 p-2", className)}
{...props}
>
{filteredData.map(children)}
</div>
</SortableContext>
<ScrollBar orientation="vertical" />
</ScrollArea>
);
};
export type KanbanHeaderProps = HTMLAttributes<HTMLDivElement>;
export const KanbanHeader = ({ className, ...props }: KanbanHeaderProps) => (
<div className={cn("m-0 p-2 font-semibold text-sm", className)} {...props} />
);
export type KanbanProviderProps<
T extends KanbanItemProps = KanbanItemProps,
C extends KanbanColumnProps = KanbanColumnProps,
> = Omit<DndContextProps, "children"> & {
children: (column: C) => ReactNode;
className?: string;
columns: C[];
data: T[];
onDataChange?: (data: T[]) => void;
onDragStart?: (event: DragStartEvent) => void;
onDragEnd?: (event: DragEndEvent) => void;
onDragOver?: (event: DragOverEvent) => void;
};
export const KanbanProvider = <
T extends KanbanItemProps = KanbanItemProps,
C extends KanbanColumnProps = KanbanColumnProps,
>({
children,
onDragStart,
onDragEnd,
onDragOver,
className,
columns,
data,
onDataChange,
...props
}: KanbanProviderProps<T, C>) => {
const [activeCardId, setActiveCardId] = useState<string | null>(null);
const sensors = useSensors(
useSensor(MouseSensor),
useSensor(TouchSensor),
useSensor(KeyboardSensor),
);
const handleDragStart = (event: DragStartEvent) => {
const card = data.find((item) => item.id === event.active.id);
if (card) {
setActiveCardId(event.active.id as string);
}
onDragStart?.(event);
};
const handleDragOver = (event: DragOverEvent) => {
const { active, over } = event;
if (!over) {
return;
}
const activeItem = data.find((item) => item.id === active.id);
const overItem = data.find((item) => item.id === over.id);
if (!activeItem) {
return;
}
const activeColumn = activeItem.column;
const overColumn =
overItem?.column ||
columns.find((col) => col.id === over.id)?.id ||
columns[0]?.id;
if (!overColumn) {
return;
}
if (activeColumn !== overColumn) {
let newData = [...data];
const activeIndex = newData.findIndex((item) => item.id === active.id);
const overIndex = newData.findIndex((item) => item.id === over.id);
if (newData[activeIndex] === undefined) {
return;
}
newData[activeIndex].column = overColumn; // Now TypeScript knows this is a string
newData = arrayMove(newData, activeIndex, overIndex);
onDataChange?.(newData);
}
onDragOver?.(event);
};
const handleDragEnd = (event: DragEndEvent) => {
setActiveCardId(null);
onDragEnd?.(event);
const { active, over } = event;
if (!over || active.id === over.id) {
return;
}
let newData = [...data];
const oldIndex = newData.findIndex((item) => item.id === active.id);
const newIndex = newData.findIndex((item) => item.id === over.id);
newData = arrayMove(newData, oldIndex, newIndex);
onDataChange?.(newData);
};
const announcements: Announcements = {
onDragStart({ active }) {
const { name, column } = data.find((item) => item.id === active.id) ?? {};
return `Picked up the card "${name}" from the "${column}" column`;
},
onDragOver({ active, over }) {
const { name } = data.find((item) => item.id === active.id) ?? {};
const newColumn = columns.find((column) => column.id === over?.id)?.name;
return `Dragged the card "${name}" over the "${newColumn}" column`;
},
onDragEnd({ active, over }) {
const { name } = data.find((item) => item.id === active.id) ?? {};
const newColumn = columns.find((column) => column.id === over?.id)?.name;
return `Dropped the card "${name}" into the "${newColumn}" column`;
},
onDragCancel({ active }) {
const { name } = data.find((item) => item.id === active.id) ?? {};
return `Cancelled dragging the card "${name}"`;
},
};
return (
<KanbanContext.Provider value={{ columns, data, activeCardId }}>
<DndContext
accessibility={{ announcements }}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
onDragStart={handleDragStart}
sensors={sensors}
{...props}
>
<div
className={cn(
"grid size-full auto-cols-fr grid-flow-col gap-4",
className,
)}
>
{columns.map((column) => children(column))}
</div>
{typeof window !== "undefined" &&
createPortal(
<DragOverlay>
<t.Out />
</DragOverlay>,
document.body,
)}
</DndContext>
</KanbanContext.Provider>
);
};

View file

@ -605,7 +605,6 @@ export const en: LangDict = {
{
icon: "cog",
items: [
{ href: "/area-riservata/admin/potenziali", title: "Potenziali" },
{ href: "/area-riservata/admin/utenti", title: "Users" },
{ href: "/area-riservata/admin/chats", title: "Chat List" },
{ href: "/area-riservata/admin/ordini", title: "Orders" },

View file

@ -610,7 +610,6 @@ export const it: LangDict = {
{
icon: "cog",
items: [
{ href: "/area-riservata/admin/potenziali", title: "Potenziali" },
{ href: "/area-riservata/admin/utenti", title: "Utenti" },
{ href: "/area-riservata/admin/chats", title: "Lista Chat" },
{ href: "/area-riservata/admin/ordini", title: "Ordini" },

View file

@ -1,53 +0,0 @@
import dynamic from "next/dynamic";
import { AreaRiservataLayout } from "~/components/Layout";
import { LoadingPage } from "~/components/loading";
import type { NextPageWithLayout } from "~/pages/_app";
const Potenziali = dynamic(
() => import("~/components/area-riservata/potenziali"),
{
loading: () => <LoadingPage />,
ssr: false,
},
);
const AdminPotenziali: NextPageWithLayout = () => {
return (
<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="flex max-w-lg items-center gap-3">
<h3 className="font-bold text-accent-foreground text-xl sm:text-2xl">
Potenziali
</h3>
</div>
</div>
<div className="p-4">
<Potenziali />
</div>
</div>
</div>
);
};
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>;
};

View file

@ -1,66 +0,0 @@
import { createContext, useContext, useState } from "react";
import type { UsersId } from "~/schemas/public/Users";
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;
};
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);
const getEditingItem = () => {
return data.find((item) => item.id === editingId) || null;
};
const addItem = (item: ElementoPotenziali) => {
setData((prev) => [...prev, item]);
};
const removeItem = (id: string) => {
setData((prev) => prev.filter((item) => item.id !== id));
};
return (
<PotenzialiContext.Provider
value={{
data,
editingId,
updateData: setData,
setEditingId,
getEditingItem,
addItem,
removeItem,
}}
>
{children}
</PotenzialiContext.Provider>
);
};
export const usePotenziali = () => {
const context = useContext(PotenzialiContext);
if (!context) {
throw new Error("usePotenziali must be used within a PotenzialiProvider");
}
return context;
};