infoalloggi-monorepo/apps/infoalloggi/src/components/etichette.tsx

193 lines
4.6 KiB
TypeScript
Raw Normal View History

2025-08-28 18:27:07 +02:00
import { Plus } from "lucide-react";
2025-08-04 17:45:44 +02:00
import { useState } from "react";
2025-08-28 18:27:07 +02:00
import { Button } from "~/components/ui/button";
2025-08-04 17:45:44 +02:00
import {
2025-08-28 18:27:07 +02:00
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
2025-08-04 17:45:44 +02:00
} from "~/components/ui/dialog";
import {
2025-08-28 18:27:07 +02:00
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
2025-08-04 17:45:44 +02:00
} from "~/components/ui/select";
2025-08-28 18:27:07 +02:00
import { Separator } from "~/components/ui/separator";
import { cn } from "~/lib/utils";
import type { ChatsChatid } from "~/schemas/public/Chats";
import type { EtichetteIdEtichetta } from "~/schemas/public/Etichette";
import { api } from "~/utils/api";
2025-08-04 17:45:44 +02:00
export const EtichetteDisplayer = ({
2025-08-28 18:27:07 +02:00
chatId,
className,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
chatId: ChatsChatid;
className?: string;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
const { data, isLoading } = api.chat.getChatEtichette.useQuery({
chatId,
});
const [openEdit, setOpenEdit] = useState(false);
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
const handleOpen = (status: boolean) => {
setOpenEdit(status);
};
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
if ((!isLoading && !data) || !data) return null;
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
return (
<div className={cn("flex flex-wrap gap-2", className)}>
{data.map((etichetta) => (
<Etichetta
2025-08-29 16:18:32 +02:00
className="text-xs"
color_hex={etichetta.color_hex}
2025-08-28 18:27:07 +02:00
key={etichetta.id_etichetta}
title={etichetta.title}
/>
))}
<button
className="flex items-center justify-center rounded-lg border p-1"
2025-08-29 16:18:32 +02:00
onClick={() => setOpenEdit(true)}
type="button"
2025-08-28 18:27:07 +02:00
>
<Plus className="size-4" />
</button>
<EtichetteModal
chatId={chatId}
onOpenChange={handleOpen}
2025-08-29 16:18:32 +02:00
open={openEdit}
2025-08-28 18:27:07 +02:00
/>
</div>
);
2025-08-04 17:45:44 +02:00
};
export const EtichetteModal = ({
2025-08-28 18:27:07 +02:00
chatId,
open,
onOpenChange,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
chatId: ChatsChatid;
open: boolean;
onOpenChange: (status: boolean) => void;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
const [selected, setSelected] = useState<EtichetteIdEtichetta | null>(null);
const utils = api.useUtils();
const { data } = api.chat.getChatEtichette.useQuery({ chatId });
const { data: etichette } = api.settings.getAllEtichette.useQuery();
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
const { mutate: remove } = api.chat.removeEtichettaFromChat.useMutation({
onSuccess: async () => {
await utils.chat.getChatEtichette.invalidate({ chatId });
await utils.chat.getActiveChats.invalidate();
},
});
const { mutate: add } = api.chat.addEtichettaToChat.useMutation({
onSuccess: async () => {
await utils.chat.getChatEtichette.invalidate({ chatId });
await utils.chat.getActiveChats.invalidate();
},
});
if (!data) return null;
return (
2025-08-29 16:18:32 +02:00
<Dialog onOpenChange={onOpenChange} open={open}>
2025-08-28 18:27:07 +02:00
<DialogContent>
<DialogHeader>
<DialogTitle>Etichette Attive</DialogTitle>
<DialogDescription className="sr-only">Attive</DialogDescription>
</DialogHeader>
<div className="space-y-5">
<div className="flex flex-col gap-2">
{data.map((etichetta, idx) => (
2025-08-29 16:18:32 +02:00
<div className="flex gap-2" key={idx}>
2025-08-28 18:27:07 +02:00
<Etichetta
color_hex={etichetta.color_hex}
2025-08-29 16:18:32 +02:00
title={etichetta.title}
2025-08-28 18:27:07 +02:00
/>
<Button
onClick={() => {
remove({ chatId, etichettaId: etichetta.id_etichetta });
}}
>
Rimuovi
</Button>
</div>
))}
</div>
<Separator />
<div className="flex gap-2">
<Select
onValueChange={(value) =>
setSelected(parseInt(value) as EtichetteIdEtichetta)
}
>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Seleziona" />
</SelectTrigger>
<SelectContent>
{etichette?.map((etichetta, idx) => (
<SelectItem key={idx} value={String(etichetta.id_etichetta)}>
{etichetta.title}
</SelectItem>
))}
</SelectContent>
</Select>
<Button
onClick={() => {
if (selected) {
add({ chatId, etichettaId: selected });
}
}}
>
Aggiungi
</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
2025-08-04 17:45:44 +02:00
};
export const Etichetta = ({
2025-08-28 18:27:07 +02:00
title,
color_hex,
className,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
title: string;
color_hex: string;
className?: string;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
return (
<span
className={cn(
2025-10-10 16:18:43 +02:00
"flex w-fit select-none items-center justify-center whitespace-nowrap rounded-md px-1.5 text-sm",
2025-08-28 18:27:07 +02:00
className,
)}
style={{
backgroundColor: color_hex,
color: colorIsDarkSimple(color_hex) ? "#FFFFFF" : "#000000",
}}
>
{title}
</span>
);
2025-08-04 17:45:44 +02:00
};
function hexToRgb(hex: string): { r: number; g: number; b: number } {
2025-08-28 18:27:07 +02:00
const bigint = parseInt(hex.slice(1), 16);
return {
b: bigint & 255,
2025-08-29 16:18:32 +02:00
g: (bigint >> 8) & 255,
r: (bigint >> 16) & 255,
2025-08-28 18:27:07 +02:00
};
2025-08-04 17:45:44 +02:00
}
export function colorIsDarkSimple(color: string) {
2025-08-28 18:27:07 +02:00
if (color === "#FFFFFF") return false;
const { r, g, b } = hexToRgb(color);
return r * 0.299 + g * 0.587 + b * 0.114 <= 186;
2025-08-04 17:45:44 +02:00
}