feat: add note field to Users schema and implement NoteUtenteDialog for user notes management
This commit is contained in:
parent
58ea254180
commit
5327cc86c6
4 changed files with 73 additions and 6 deletions
|
|
@ -3,6 +3,7 @@ import {
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
Mail,
|
Mail,
|
||||||
MessagesSquare,
|
MessagesSquare,
|
||||||
|
NotebookPen,
|
||||||
Paperclip,
|
Paperclip,
|
||||||
Search,
|
Search,
|
||||||
ShoppingBag,
|
ShoppingBag,
|
||||||
|
|
@ -12,7 +13,18 @@ import {
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
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 LoadingButton from "~/components/custom_ui/loading-button";
|
||||||
|
import { Textarea } from "~/components/custom_ui/textarea";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "~/components/ui/dialog";
|
||||||
import { useUserViewContext } from "~/lib/userViewContext";
|
import { useUserViewContext } from "~/lib/userViewContext";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { Confirm } from "../confirm";
|
import { Confirm } from "../confirm";
|
||||||
|
|
@ -45,7 +57,7 @@ export const UserViewHeader = () => {
|
||||||
if (!data) return <div>Errore</div>;
|
if (!data) return <div>Errore</div>;
|
||||||
return (
|
return (
|
||||||
<div className="mb-5 flex flex-col space-y-5">
|
<div className="mb-5 flex flex-col space-y-5">
|
||||||
<div className="flex flex-row flex-wrap items-baseline gap-3">
|
<div className="flex flex-row flex-wrap items-center gap-3">
|
||||||
<div className="text-3xl">{data.username}</div>
|
<div className="text-3xl">{data.username}</div>
|
||||||
|
|
||||||
<Link href={`https://wa.me/${data.telefono}`} target="_blank">
|
<Link href={`https://wa.me/${data.telefono}`} target="_blank">
|
||||||
|
|
@ -72,6 +84,7 @@ export const UserViewHeader = () => {
|
||||||
<Mail /> Invita al sito
|
<Mail /> Invita al sito
|
||||||
</Button>
|
</Button>
|
||||||
</Confirm>
|
</Confirm>
|
||||||
|
<NoteUtenteDialog />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row flex-wrap gap-2">
|
<div className="flex flex-row flex-wrap gap-2">
|
||||||
<Link href={`/area-riservata/admin/user-view/edit-user/${data.id}`}>
|
<Link href={`/area-riservata/admin/user-view/edit-user/${data.id}`}>
|
||||||
|
|
@ -205,8 +218,8 @@ const IntestazioneMaker = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format names
|
// Format names
|
||||||
const cognome = titleCase(data.cognome);
|
const cognome = data.cognome.toUpperCase();
|
||||||
const nome = titleCase(data.nome);
|
const nome = data.nome.toUpperCase();
|
||||||
const viaResidenza = titleCase(data.via_residenza);
|
const viaResidenza = titleCase(data.via_residenza);
|
||||||
const comuneNascita = titleCase(comune_nascita.nome);
|
const comuneNascita = titleCase(comune_nascita.nome);
|
||||||
const comuneResidenza = titleCase(comune_residenza.nome);
|
const comuneResidenza = titleCase(comune_residenza.nome);
|
||||||
|
|
@ -260,3 +273,51 @@ function titleCase(str: string | null | undefined): string {
|
||||||
})
|
})
|
||||||
.join(" ");
|
.join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const NoteUtenteDialog = () => {
|
||||||
|
const { id, note } = useUserViewContext();
|
||||||
|
const [newNote, setNewNote] = useState(note || "");
|
||||||
|
const utils = api.useUtils();
|
||||||
|
const { mutate, isPending } = api.users.editUser.useMutation({
|
||||||
|
onSuccess: async () => {
|
||||||
|
toast.success("Note aggiornate con successo");
|
||||||
|
await utils.users.invalidate();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button size="sm" variant="orange">
|
||||||
|
<NotebookPen className="size-4" /> Note
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-2xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Note Utente</DialogTitle>
|
||||||
|
<DialogDescription className="sr-only">note</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex w-full flex-col gap-4">
|
||||||
|
<Textarea
|
||||||
|
className="min-h-64"
|
||||||
|
id="note"
|
||||||
|
onChange={(e) => setNewNote(e.target.value)}
|
||||||
|
placeholder=""
|
||||||
|
value={newNote}
|
||||||
|
/>
|
||||||
|
<LoadingButton
|
||||||
|
disabled={newNote === note}
|
||||||
|
loading={isPending}
|
||||||
|
onClick={() => mutate({ id, data: { note: newNote } })}
|
||||||
|
variant="success"
|
||||||
|
>
|
||||||
|
Salva
|
||||||
|
</LoadingButton>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ export default interface UsersTable {
|
||||||
salt: ColumnType<string, string, string>;
|
salt: ColumnType<string, string, string>;
|
||||||
|
|
||||||
mustChangePassword: ColumnType<boolean, boolean | undefined, boolean>;
|
mustChangePassword: ColumnType<boolean, boolean | undefined, boolean>;
|
||||||
|
|
||||||
|
note: ColumnType<string | null, string | null, string | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Users = Selectable<UsersTable>;
|
export type Users = Selectable<UsersTable>;
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
getEmails,
|
getEmails,
|
||||||
} from "~/server/services/email.service";
|
} from "~/server/services/email.service";
|
||||||
import { NewMail } from "~/server/services/mailer";
|
import { NewMail } from "~/server/services/mailer";
|
||||||
|
import { CONDIZIONI_DEFAULT } from "~/server/services/prezziario.service";
|
||||||
import { genPdfSchedaAnnuncioBase64 } from "~/server/services/puppeteer.service";
|
import { genPdfSchedaAnnuncioBase64 } from "~/server/services/puppeteer.service";
|
||||||
import { zAnnuncioId, zOrdineId, zUserId } from "~/server/utils/zod_types";
|
import { zAnnuncioId, zOrdineId, zUserId } from "~/server/utils/zod_types";
|
||||||
|
|
||||||
|
|
@ -246,7 +247,7 @@ export const comunicazioniRouter = createTRPCRouter({
|
||||||
generate: {
|
generate: {
|
||||||
type: "condizioni",
|
type: "condizioni",
|
||||||
stringId: (ordine.testo_condizioni ||
|
stringId: (ordine.testo_condizioni ||
|
||||||
"CONDIZIONI_CERCHI") as TestiEStringheStingaId,
|
CONDIZIONI_DEFAULT) as TestiEStringheStingaId,
|
||||||
},
|
},
|
||||||
encoding: "base64",
|
encoding: "base64",
|
||||||
contentType: "application/pdf",
|
contentType: "application/pdf",
|
||||||
|
|
@ -266,7 +267,7 @@ export const comunicazioniRouter = createTRPCRouter({
|
||||||
userId: ordine.userid,
|
userId: ordine.userid,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
lock_expires: new Date(), // Lock expires in 1
|
lock_expires: new Date(Date.now() + 3000), // Lock for 30 sec
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
import { apiProcedure, createTRPCRouter } from "~/server/api/trpc";
|
import {
|
||||||
|
apiProcedure,
|
||||||
|
createTRPCRouter,
|
||||||
|
} from "~/server/api/trpc";
|
||||||
import { processEvents } from "~/server/controllers/event_queue.controller";
|
import { processEvents } from "~/server/controllers/event_queue.controller";
|
||||||
|
|
||||||
export const eventQueueRouter = createTRPCRouter({
|
export const eventQueueRouter = createTRPCRouter({
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue