feat: implement user labels management with CRUD operations and UI integration
This commit is contained in:
parent
eedaab6e9b
commit
a04e3c2fd3
8 changed files with 322 additions and 11 deletions
34
apps/db/migrations/36_user_etichette.up.sql
Normal file
34
apps/db/migrations/36_user_etichette.up.sql
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS public.user_etichette (
|
||||||
|
"userId" UUID NOT NULL,
|
||||||
|
"etichettaId" integer NOT NULL,
|
||||||
|
);
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE public.user_etichette
|
||||||
|
ADD CONSTRAINT user_etichette_pkey PRIMARY KEY ("userId", "etichettaId");
|
||||||
|
EXCEPTION
|
||||||
|
WHEN invalid_table_definition THEN
|
||||||
|
RAISE NOTICE 'Primary key already exists. Ignoring...';
|
||||||
|
END $$;
|
||||||
|
-- Foreign Key to Chats
|
||||||
|
DO $$ BEGIN IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint
|
||||||
|
WHERE conname = 'user_etichetta_fkey'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.user_etichette
|
||||||
|
ADD CONSTRAINT user_etichetta_fkey
|
||||||
|
FOREIGN KEY ("userId") REFERENCES public.users (id)
|
||||||
|
ON UPDATE CASCADE ON DELETE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
-- Foreign Key to Etichette
|
||||||
|
DO $$ BEGIN IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint
|
||||||
|
WHERE conname = 'etichetta_user_etichetta_fkey'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE public.user_etichette
|
||||||
|
ADD CONSTRAINT etichetta_user_etichetta_fkey
|
||||||
|
FOREIGN KEY ("etichettaId") REFERENCES public.etichette (id_etichetta)
|
||||||
|
ON UPDATE CASCADE ON DELETE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
@ -113,7 +113,7 @@ export const EtichetteModal = ({
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
remove({ chatId, etichettaId: etichetta.id_etichetta });
|
remove({ chatId, etichettaId: etichetta.id_etichetta });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Rimuovi
|
Rimuovi
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,17 @@ import {
|
||||||
Mail,
|
Mail,
|
||||||
MessagesSquare,
|
MessagesSquare,
|
||||||
Paperclip,
|
Paperclip,
|
||||||
|
Plus,
|
||||||
Search,
|
Search,
|
||||||
ShoppingBag,
|
ShoppingBag,
|
||||||
|
Tags,
|
||||||
User,
|
User,
|
||||||
UserCog,
|
UserCog,
|
||||||
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { DataTable } from "~/components/custom_ui/data-table";
|
import { DataTable } from "~/components/custom_ui/data-table";
|
||||||
import { DataTableColumnHeader } from "~/components/custom_ui/dataTable-header";
|
import { DataTableColumnHeader } from "~/components/custom_ui/dataTable-header";
|
||||||
|
|
@ -20,15 +24,26 @@ import type {
|
||||||
} from "~/components/custom_ui/dataTable-toolbar";
|
} from "~/components/custom_ui/dataTable-toolbar";
|
||||||
import { UserAvatar } from "~/components/user_avatar";
|
import { UserAvatar } from "~/components/user_avatar";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import type { Users } from "~/schemas/public/Users";
|
import type { Etichette } from "~/schemas/public/Etichette";
|
||||||
|
import type { Users, UsersId } from "~/schemas/public/Users";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
import { Etichetta } from "../etichette";
|
||||||
import { WhatsAppIcon2 } from "../svgs";
|
import { WhatsAppIcon2 } from "../svgs";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
import {
|
import {
|
||||||
ContextMenuContent,
|
ContextMenuContent,
|
||||||
ContextMenuItem,
|
ContextMenuItem,
|
||||||
ContextMenuLabel,
|
ContextMenuLabel,
|
||||||
ContextMenuSeparator,
|
ContextMenuSeparator,
|
||||||
} from "../ui/context-menu";
|
} from "../ui/context-menu";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "../ui/dialog";
|
||||||
|
import { Separator } from "../ui/separator";
|
||||||
|
|
||||||
type UserWChatInfo = Pick<
|
type UserWChatInfo = Pick<
|
||||||
Users,
|
Users,
|
||||||
|
|
@ -39,7 +54,7 @@ type UserWChatInfo = Pick<
|
||||||
| "isAdmin"
|
| "isAdmin"
|
||||||
| "isBlocked"
|
| "isBlocked"
|
||||||
| "created_at"
|
| "created_at"
|
||||||
> & { chatid: string | null };
|
> & { chatid: string | null; etichette: Etichette[] };
|
||||||
|
|
||||||
export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -50,6 +65,11 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [selected, setSelected] = useState<{
|
||||||
|
id: UsersId;
|
||||||
|
etichette: Etichette[];
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
const { data } = props;
|
const { data } = props;
|
||||||
|
|
||||||
const tabledata = data.map((user) => {
|
const tabledata = data.map((user) => {
|
||||||
|
|
@ -60,8 +80,9 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
||||||
telefono: user.telefono,
|
telefono: user.telefono,
|
||||||
id: user.id,
|
id: user.id,
|
||||||
isAdmin: user.isAdmin ? "Admin" : "Utente",
|
isAdmin: user.isAdmin ? "Admin" : "Utente",
|
||||||
isBanned: user.isBlocked ? "Bloccato" : "Attivo",
|
isBlocked: user.isBlocked ? "Bloccato" : "Attivo",
|
||||||
username: user.username,
|
username: user.username,
|
||||||
|
etichette: user.etichette,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -75,12 +96,37 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
||||||
email: "Email",
|
email: "Email",
|
||||||
telefono: "Telefono",
|
telefono: "Telefono",
|
||||||
isAdmin: "Ruolo",
|
isAdmin: "Ruolo",
|
||||||
isBanned: "Status",
|
isBlocked: "Status",
|
||||||
username: "User",
|
username: "User",
|
||||||
|
etichette: "📎",
|
||||||
};
|
};
|
||||||
type Column = (typeof tabledata)[number];
|
type Column = (typeof tabledata)[number];
|
||||||
|
|
||||||
const columns: ColumnDef<Column>[] = [
|
const columns: ColumnDef<Column>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: "etichette",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
column={column}
|
||||||
|
title={columns_titles.etichette}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex max-w-32 flex-wrap items-center gap-2">
|
||||||
|
{row.original.etichette.map((e) => (
|
||||||
|
<Etichetta
|
||||||
|
color_hex={e.color_hex}
|
||||||
|
key={e.id_etichetta}
|
||||||
|
title={e.title}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
enableHiding: false,
|
||||||
|
enableSorting: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "username",
|
accessorKey: "username",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
|
|
@ -170,14 +216,14 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
accessorKey: "isBanned",
|
accessorKey: "isBlocked",
|
||||||
filterFn: (row, id, value) => {
|
filterFn: (row, id, value) => {
|
||||||
return value.includes(row.getValue(id));
|
return value.includes(row.getValue(id));
|
||||||
},
|
},
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<DataTableColumnHeader
|
<DataTableColumnHeader
|
||||||
column={column}
|
column={column}
|
||||||
title={columns_titles.isBanned}
|
title={columns_titles.isBlocked}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
@ -192,7 +238,7 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
||||||
title: "Ruolo",
|
title: "Ruolo",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
columnName: "isBanned",
|
columnName: "isBlocked",
|
||||||
options: [
|
options: [
|
||||||
{ label: "Bloccato", value: "Bloccato" },
|
{ label: "Bloccato", value: "Bloccato" },
|
||||||
{ label: "Attivo", value: "Attivo" },
|
{ label: "Attivo", value: "Attivo" },
|
||||||
|
|
@ -307,6 +353,17 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
||||||
<User /> Login
|
<User /> Login
|
||||||
</button>
|
</button>
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
|
<ContextMenuItem>
|
||||||
|
<button
|
||||||
|
className="flex w-full cursor-pointer items-center gap-2"
|
||||||
|
onClick={() => {
|
||||||
|
setSelected({ id: data.id, etichette: data.etichette });
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<Tags /> Etichette
|
||||||
|
</button>
|
||||||
|
</ContextMenuItem>
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
)}
|
)}
|
||||||
data={tabledata}
|
data={tabledata}
|
||||||
|
|
@ -323,6 +380,140 @@ export const UsersTable = (props: { data: UserWChatInfo[] }) => {
|
||||||
searchColumn={searchFiltro}
|
searchColumn={searchFiltro}
|
||||||
withContextMenu={true}
|
withContextMenu={true}
|
||||||
/>
|
/>
|
||||||
|
<UsersEtichetteModal selected={selected} setSelected={setSelected} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const UsersEtichetteModal = ({
|
||||||
|
selected,
|
||||||
|
setSelected,
|
||||||
|
}: {
|
||||||
|
selected: { id: UsersId; etichette: Etichette[] } | null;
|
||||||
|
setSelected: React.Dispatch<
|
||||||
|
React.SetStateAction<{ id: UsersId; etichette: Etichette[] } | null>
|
||||||
|
>;
|
||||||
|
}) => {
|
||||||
|
const utils = api.useUtils();
|
||||||
|
|
||||||
|
const { data: etichette } = api.etichette.getAllEtichette.useQuery();
|
||||||
|
|
||||||
|
const { mutate: addEtichetta } = api.etichette.addUserEtichetta.useMutation({
|
||||||
|
onSuccess: async () => {
|
||||||
|
toast.success("Etichetta aggiunta con successo");
|
||||||
|
await utils.etichette.getAllEtichette.invalidate();
|
||||||
|
await utils.users.getUsersWChatInfo.invalidate();
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast.error("Errore nell'aggiunta dell'etichetta");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { mutate: removeEtichetta } =
|
||||||
|
api.etichette.removeUserEtichetta.useMutation({
|
||||||
|
onSuccess: async () => {
|
||||||
|
toast.success("Etichetta rimossa con successo");
|
||||||
|
await utils.etichette.getAllEtichette.invalidate();
|
||||||
|
await utils.users.getUsersWChatInfo.invalidate();
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast.error("Errore nella rimozione dell'etichetta");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
onOpenChange={(v) => {
|
||||||
|
if (!v) setSelected(null);
|
||||||
|
}}
|
||||||
|
open={!!selected}
|
||||||
|
>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Etichette</DialogTitle>
|
||||||
|
<DialogDescription className="sr-only">Etichette</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-5">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<h3>Etichette Attive</h3>
|
||||||
|
{selected?.etichette.map((etichetta) => (
|
||||||
|
<div
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
key={`${etichetta.id_etichetta}${selected.id}`}
|
||||||
|
>
|
||||||
|
<Etichetta
|
||||||
|
color_hex={etichetta.color_hex}
|
||||||
|
title={etichetta.title}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
removeEtichetta({
|
||||||
|
userId: selected.id,
|
||||||
|
etichettaId: etichetta.id_etichetta,
|
||||||
|
});
|
||||||
|
setSelected((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
etichette: prev.etichette.filter(
|
||||||
|
(e) => e.id_etichetta !== etichetta.id_etichetta,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
size="sm"
|
||||||
|
variant="destructive"
|
||||||
|
>
|
||||||
|
<X className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<h3>Aggiungi Etichetta</h3>
|
||||||
|
{etichette
|
||||||
|
?.filter(
|
||||||
|
(e) =>
|
||||||
|
!selected?.etichette.some(
|
||||||
|
(etichetta) => etichetta.id_etichetta === e.id_etichetta,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map((e) => (
|
||||||
|
<div className="flex items-center gap-2" key={e.id_etichetta}>
|
||||||
|
<Etichetta color_hex={e.color_hex} title={e.title} />
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (selected) {
|
||||||
|
addEtichetta({
|
||||||
|
userId: selected.id,
|
||||||
|
etichettaId: e.id_etichetta,
|
||||||
|
});
|
||||||
|
setSelected((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
etichette: [
|
||||||
|
...prev.etichette,
|
||||||
|
{
|
||||||
|
id_etichetta: e.id_etichetta,
|
||||||
|
color_hex: e.color_hex,
|
||||||
|
title: e.title,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
size="sm"
|
||||||
|
variant="success"
|
||||||
|
>
|
||||||
|
<Plus className="size-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import type { default as AnnunciTable } from './Annunci';
|
||||||
import type { default as UsersTable } from './Users';
|
import type { default as UsersTable } from './Users';
|
||||||
import type { default as ChatsEtichetteTable } from './ChatsEtichette';
|
import type { default as ChatsEtichetteTable } from './ChatsEtichette';
|
||||||
import type { default as TestiEStringheTable } from './TestiEStringhe';
|
import type { default as TestiEStringheTable } from './TestiEStringhe';
|
||||||
|
import type { default as UserEtichetteTable } from './UserEtichette';
|
||||||
import type { default as UsersStorageTable } from './UsersStorage';
|
import type { default as UsersStorageTable } from './UsersStorage';
|
||||||
import type { default as ServizioAnnunciTable } from './ServizioAnnunci';
|
import type { default as ServizioAnnunciTable } from './ServizioAnnunci';
|
||||||
import type { default as UserInvitesTable } from './UserInvites';
|
import type { default as UserInvitesTable } from './UserInvites';
|
||||||
|
|
@ -43,6 +44,8 @@ export default interface PublicSchema {
|
||||||
|
|
||||||
testi_e_stringhe: TestiEStringheTable;
|
testi_e_stringhe: TestiEStringheTable;
|
||||||
|
|
||||||
|
user_etichette: UserEtichetteTable;
|
||||||
|
|
||||||
users_storage: UsersStorageTable;
|
users_storage: UsersStorageTable;
|
||||||
|
|
||||||
servizio_annunci: ServizioAnnunciTable;
|
servizio_annunci: ServizioAnnunciTable;
|
||||||
|
|
|
||||||
19
apps/infoalloggi/src/schemas/public/UserEtichette.ts
Normal file
19
apps/infoalloggi/src/schemas/public/UserEtichette.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
// @generated
|
||||||
|
// This file is automatically generated by Kanel. Do not modify manually.
|
||||||
|
|
||||||
|
import type { UsersId } from './Users';
|
||||||
|
import type { EtichetteIdEtichetta } from './Etichette';
|
||||||
|
import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely';
|
||||||
|
|
||||||
|
/** Represents the table public.user_etichette */
|
||||||
|
export default interface UserEtichetteTable {
|
||||||
|
userId: ColumnType<UsersId, UsersId, UsersId>;
|
||||||
|
|
||||||
|
etichettaId: ColumnType<EtichetteIdEtichetta, EtichetteIdEtichetta, EtichetteIdEtichetta>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserEtichette = Selectable<UserEtichetteTable>;
|
||||||
|
|
||||||
|
export type NewUserEtichette = Insertable<UserEtichetteTable>;
|
||||||
|
|
||||||
|
export type UserEtichetteUpdate = Updateable<UserEtichetteTable>;
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
import { adminProcedure, createTRPCRouter } from "~/server/api/trpc";
|
import { adminProcedure, createTRPCRouter } from "~/server/api/trpc";
|
||||||
import {
|
import {
|
||||||
|
|
@ -7,7 +8,7 @@ import {
|
||||||
updateEtichetta,
|
updateEtichetta,
|
||||||
} from "~/server/controllers/etichette.controller";
|
} from "~/server/controllers/etichette.controller";
|
||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
import { zEtichettaId } from "~/server/utils/zod_types";
|
import { zEtichettaId, zUserId } from "~/server/utils/zod_types";
|
||||||
|
|
||||||
export const etichetteRouter = createTRPCRouter({
|
export const etichetteRouter = createTRPCRouter({
|
||||||
addEtichetta: adminProcedure
|
addEtichetta: adminProcedure
|
||||||
|
|
@ -51,4 +52,49 @@ export const etichetteRouter = createTRPCRouter({
|
||||||
title: input.title,
|
title: input.title,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
addUserEtichetta: adminProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
userId: zUserId,
|
||||||
|
etichettaId: zEtichettaId,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
try {
|
||||||
|
await db
|
||||||
|
.insertInto("user_etichette")
|
||||||
|
.values({
|
||||||
|
etichettaId: input.etichettaId,
|
||||||
|
userId: input.userId,
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
} catch (error) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Errore aggiunta etichetta all'utente: ${error instanceof Error ? error.message : "Errore sconosciuto"}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
removeUserEtichetta: adminProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
userId: zUserId,
|
||||||
|
etichettaId: zEtichettaId,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
try {
|
||||||
|
await db
|
||||||
|
.deleteFrom("user_etichette")
|
||||||
|
.where("etichettaId", "=", input.etichettaId)
|
||||||
|
.where("userId", "=", input.userId)
|
||||||
|
.execute();
|
||||||
|
} catch (error) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Errore rimozione etichetta all'utente: ${error instanceof Error ? error.message : "Errore sconosciuto"}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return "ok";
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,6 @@ export const usersRouter = createTRPCRouter({
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getUsersWChatInfo: adminProcedure.query(async () => {
|
getUsersWChatInfo: adminProcedure.query(async () => {
|
||||||
return await getUsersWithChatInfoHandler({ db });
|
return await getUsersWithChatInfoHandler();
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import { jsonArrayFrom } from "kysely/helpers/postgres";
|
||||||
import type { UsersId, UsersUpdate } from "~/schemas/public/Users";
|
import type { UsersId, UsersUpdate } from "~/schemas/public/Users";
|
||||||
import type { UsersAnagraficaUpdate } from "~/schemas/public/UsersAnagrafica";
|
import type { UsersAnagraficaUpdate } from "~/schemas/public/UsersAnagrafica";
|
||||||
import { db, type Querier } from "~/server/db";
|
import { db, type Querier } from "~/server/db";
|
||||||
|
|
@ -167,7 +168,7 @@ export const getActiveUserWithoutChatHandler = async ({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUsersWithChatInfoHandler = async ({ db }: { db: Querier }) => {
|
export const getUsersWithChatInfoHandler = async () => {
|
||||||
return await db
|
return await db
|
||||||
.selectFrom("users")
|
.selectFrom("users")
|
||||||
.select([
|
.select([
|
||||||
|
|
@ -181,6 +182,23 @@ export const getUsersWithChatInfoHandler = async ({ db }: { db: Querier }) => {
|
||||||
])
|
])
|
||||||
.leftJoin("chats", "chats.user_ref", "users.id")
|
.leftJoin("chats", "chats.user_ref", "users.id")
|
||||||
.select(["chats.chatid"])
|
.select(["chats.chatid"])
|
||||||
|
.select((eb) =>
|
||||||
|
jsonArrayFrom(
|
||||||
|
eb
|
||||||
|
.selectFrom("etichette")
|
||||||
|
.innerJoin(
|
||||||
|
"user_etichette",
|
||||||
|
"user_etichette.etichettaId",
|
||||||
|
"etichette.id_etichetta",
|
||||||
|
)
|
||||||
|
.whereRef("user_etichette.userId", "=", "users.id")
|
||||||
|
.select([
|
||||||
|
"etichette.id_etichetta",
|
||||||
|
"etichette.title",
|
||||||
|
"etichette.color_hex",
|
||||||
|
]),
|
||||||
|
).as("etichette"),
|
||||||
|
)
|
||||||
.orderBy("users.id", "desc")
|
.orderBy("users.id", "desc")
|
||||||
.select(["users.id as id"])
|
.select(["users.id as id"])
|
||||||
.execute();
|
.execute();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue