infoalloggi-monorepo/apps/infoalloggi/src/components/area-riservata/potenziali.tsx

453 lines
12 KiB
TypeScript
Raw Normal View History

"use client";
import { format } from "date-fns";
import { Plus, Trash2 } from "lucide-react";
import { useEffect, useState } from "react";
import {
KanbanBoard,
KanbanCard,
KanbanCards,
KanbanHeader,
KanbanProvider,
} from "~/components/ui/kanban";
import { usePotenziali } from "~/providers/PotenzialiProvider";
import type { PotenzialiGroupsId } from "~/schemas/public/PotenzialiGroups";
import type { UsersId } from "~/schemas/public/Users";
import type { PotenzialeData } from "~/server/services/potenziali.service";
import { api } from "~/utils/api";
import { Confirm } from "../confirm";
import {
Credenza,
CredenzaBody,
CredenzaClose,
CredenzaContent,
CredenzaDescription,
CredenzaFooter,
CredenzaHeader,
CredenzaTitle,
CredenzaTrigger,
} from "../custom_ui/credenza";
import Input from "../custom_ui/input";
import { Textarea } from "../custom_ui/textarea";
import { Button } from "../ui/button";
import { Label } from "../ui/label";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "../ui/select";
import { UserAvatar } from "../user_avatar";
export const Potenziali = () => {
const { data, columns, setData, updateCall, removeColumn } = usePotenziali();
return (
<>
<KanbanProvider
className="w-max"
columns={columns}
data={data}
onDataChange={setData}
onDragEnd={() => {
updateCall();
}}
>
{(column) => (
<KanbanBoard
className="w-fit overflow-auto"
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>
<NewCard columnId={column.id} />
<Confirm
description="Sei sicuro di voler eliminare?"
onConfirm={() => {
removeColumn(column.id);
}}
title="Elimina"
>
<Button aria-label="Delete" variant="destructive">
<Trash2 />
</Button>
</Confirm>
</div>
</KanbanHeader>
<KanbanCards className="w-68" id={column.id}>
{(data: PotenzialeData) => (
<PotenzialeCardItem
columnId={column.id}
data={data}
key={data.id}
/>
)}
</KanbanCards>
</KanbanBoard>
)}
</KanbanProvider>
</>
);
};
// 1. Extract the card logic into a separate component
const PotenzialeCardItem = ({
data,
columnId,
}: {
data: PotenzialeData;
columnId: string;
}) => {
const [isEditingOpen, setIsEditingOpen] = useState(false);
return (
<KanbanCard
cardOnClick={() => {
setIsEditingOpen(true);
}}
column={columnId}
id={data.id}
key={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>
<p className="m-0 text-muted-foreground text-xs">
{data.descrizione}
</p>
</div>
{data.userid && (
<UserAvatar
className="size-6 shrink-0 text-sm"
userId={data.userid}
username={data.username || "Utente"}
/>
)}
</div>
<p className="mt-2 text-muted-foreground text-xs">
{format(data.created_at, "dd/MM/yyyy HH:mm")}
</p>
{/** biome-ignore lint/a11y/noNoninteractiveElementInteractions: <ok> */}
{/** biome-ignore lint/a11y/noStaticElementInteractions: <ok> */}
{/** biome-ignore lint/a11y/useKeyWithClickEvents: <ok> */}
<div onClick={(e) => e.stopPropagation()}>
<EditingItem
data={data}
open={isEditingOpen}
setOpen={setIsEditingOpen}
/>
</div>
</>
</KanbanCard>
);
};
export const NewColumn = () => {
const [newColumnName, setNewColumnName] = useState("");
const { addColumn } = usePotenziali();
const [open, setOpen] = useState(false);
return (
<Credenza onOpenChange={setOpen} open={open}>
<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("");
setOpen(false);
}}
>
Aggiungi
</Button>
</CredenzaBody>
</CredenzaContent>
</Credenza>
);
};
export const NewCard = ({ columnId }: { columnId: PotenzialiGroupsId }) => {
const [newItemName, setNewItemName] = useState("");
const [newItemDescrizione, setNewItemDescrizione] = useState("");
const [newItemUserid, setNewItemUserid] = useState<UsersId | null>(null);
const { addItem } = usePotenziali();
const [open, setOpen] = useState(false);
const resetState = () => {
setNewItemName("");
setNewItemDescrizione("");
setNewItemUserid(null);
};
const handleAddItem = () => {
addItem({
name: newItemName,
descrizione: newItemDescrizione,
potenziali_group_id: columnId,
userid: newItemUserid,
});
resetState();
setOpen(false);
};
useEffect(() => {
if (open) {
resetState();
}
}, [open]);
const { data: admins, isLoading } = api.users.getAdmins.useQuery();
return (
<Credenza onOpenChange={setOpen} open={open}>
<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
id="titolo"
onChange={(e) => setNewItemName(e.target.value)}
type="string"
value={newItemName}
/>
<Label htmlFor="descrizione">Descrizione</Label>
<Textarea
className="min-h-64"
id="descrizione"
onChange={(e) => setNewItemDescrizione(e.target.value)}
placeholder=""
value={newItemDescrizione}
/>
<Label htmlFor="utente">Utente Assegnato</Label>
{isLoading ? (
<p>Loading...</p>
) : (
<Select
onValueChange={(v) => {
setNewItemUserid(v === "empty" ? null : (v as UsersId));
}}
value={newItemUserid || "empty"}
>
<SelectTrigger
className="w-full data-[size=default]:h-10"
id="utente"
>
<SelectValue placeholder="Assegna un Utente" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Utente Assegnato</SelectLabel>
<SelectItem className="h-10" key="empty" value="empty">
nessuno
</SelectItem>
{admins?.map((admin) => (
<SelectItem key={admin.id} value={admin.id.toString()}>
<span className="flex items-center gap-2">
<UserAvatar
className="size-8"
userId={admin.id}
username={admin.username}
/>
<span>
<span className="block font-medium">
{admin.username}
</span>
</span>
</span>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
)}
</CredenzaBody>
<CredenzaFooter>
<Button disabled={!newItemName.length} onClick={handleAddItem}>
Aggiungi
</Button>
<CredenzaClose asChild>
<Button>Chiudi</Button>
</CredenzaClose>
</CredenzaFooter>
</CredenzaContent>
</Credenza>
);
};
const EditingItem = ({
open,
setOpen,
data,
}: {
open: boolean;
setOpen: (open: boolean) => void;
data: PotenzialeData;
}) => {
const { updateItem, removeItem } = usePotenziali();
const [state, setState] = useState<PotenzialeData>(data);
const [hasChanges, setHasChanges] = useState(false);
const { data: admins, isLoading } = api.users.getAdmins.useQuery();
useEffect(() => {
if (open) {
setState(data);
setHasChanges(false);
}
}, [open]);
return (
<Credenza onOpenChange={setOpen} open={open}>
<CredenzaContent className="max-h-[90vh]">
<CredenzaHeader>
<CredenzaTitle className="text-center font-semibold text-2xl">
Modifica {state.name}
</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
id="titolo"
onChange={(e) => {
setState((prev) => prev && { ...prev, name: e.target.value });
setHasChanges(true);
}}
type="string"
value={state.name}
/>
<Label htmlFor="descrizione">Descrizione</Label>
<Textarea
className="min-h-64"
id="descrizione"
onChange={(e) => {
setState(
(prev) => prev && { ...prev, descrizione: e.target.value },
);
setHasChanges(true);
}}
placeholder=""
value={state.descrizione || undefined}
/>
<Label htmlFor="utente">Utente Assegnato</Label>
{isLoading ? (
<p>Loading...</p>
) : (
<Select
onValueChange={(v) => {
setState(
(prev) =>
prev && {
...prev,
userid: v === "empty" ? null : (v as UsersId),
},
);
setHasChanges(true);
}}
value={state.userid?.toString() || "empty"}
>
<SelectTrigger
className="w-full data-[size=default]:h-10"
id="utente"
>
<SelectValue placeholder="Assegna un Utente" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Utente Assegnato</SelectLabel>
<SelectItem className="h-10" key="empty" value="empty">
nessuno
</SelectItem>
{admins?.map((admin) => (
<SelectItem key={admin.id} value={admin.id.toString()}>
<span className="flex items-center gap-2">
<UserAvatar
className="size-8"
userId={admin.id}
username={admin.username}
/>
<span>
<span className="block font-medium">
{admin.username}
</span>
</span>
</span>
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
)}
</CredenzaBody>
<CredenzaFooter>
<Button
disabled={!hasChanges || !state}
onClick={() => {
updateItem(state);
setHasChanges(false);
}}
type="button"
>
Salva
</Button>
<Confirm
description="Sei sicuro di voler eliminare?"
onConfirm={() => {
if (state) {
setOpen(false);
removeItem(state.id);
}
}}
title="Elimina"
>
<Button aria-label="Delete" variant="destructive">
<Trash2 />
<span>Elimina</span>
</Button>
</Confirm>
<CredenzaClose asChild>
<Button>Chiudi</Button>
</CredenzaClose>
</CredenzaFooter>
</CredenzaContent>
</Credenza>
);
};