feat: implement box management module with API services, UI components, and dependency updates
This commit is contained in:
@@ -69,6 +69,8 @@
|
||||
"ui_view_all_notifications": "View All Notifications",
|
||||
"ui_label_notifications": "Notifications",
|
||||
"ui_change_password_btn": "Change password",
|
||||
"ui_edit_house_btn": "Edit house",
|
||||
"ui_edit_box_btn": "Edit box",
|
||||
"nav_label_management": "Management",
|
||||
"nav_label_basic": "Basic",
|
||||
"nav_label_kanri": "Administrator",
|
||||
@@ -242,6 +244,7 @@
|
||||
"boxes_page_ui_table_header_tags": "Tags",
|
||||
"boxes_page_ui_table_header_user": "Creater",
|
||||
"boxes_page_ui_table_header_house": "House",
|
||||
"items_page_ui_title": "Vật phẩm",
|
||||
"backend_message": [
|
||||
{
|
||||
"match": {
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"ui_label_notifications": "Thông báo",
|
||||
"ui_change_password_btn": "Đổi mật khẩu",
|
||||
"ui_edit_house_btn": "Chỉnh sửa nhà",
|
||||
"ui_edit_box_btn": "Chỉnh sửa hộp chứa",
|
||||
"nav_label_management": "Quản lý",
|
||||
"nav_label_basic": "Cơ bản",
|
||||
"nav_label_kanri": "Quản trị viên",
|
||||
@@ -246,6 +247,7 @@
|
||||
"boxes_page_ui_table_header_tags": "Nhãn dán",
|
||||
"boxes_page_ui_table_header_user": "Người tạo",
|
||||
"boxes_page_ui_table_header_house": "Thuộc nhà",
|
||||
"items_page_ui_title": "Vật phẩm",
|
||||
"backend_message": [
|
||||
{
|
||||
"match": {
|
||||
|
||||
85
package.json
85
package.json
@@ -19,58 +19,57 @@
|
||||
"db:seed": "dotenv -e .env.local -- prisma db seed"
|
||||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.0.0",
|
||||
"@base-ui/react": "^1.6.0",
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@phosphor-icons/react": "^2.1.10",
|
||||
"@prisma/adapter-pg": "^7.1.0",
|
||||
"@prisma/client": "^7.4.1",
|
||||
"@tailwindcss/vite": "^4.0.6",
|
||||
"@tanstack/react-devtools": "^0.9.4",
|
||||
"@tanstack/react-form": "^1.27.7",
|
||||
"@tanstack/react-query": "^5.66.5",
|
||||
"@tanstack/react-query-devtools": "^5.84.2",
|
||||
"@tanstack/react-router": "^1.132.0",
|
||||
"@tanstack/react-router-devtools": "^1.132.0",
|
||||
"@tanstack/react-router-ssr-query": "^1.131.7",
|
||||
"@tanstack/react-start": "^1.132.0",
|
||||
"@prisma/adapter-pg": "^7.8.0",
|
||||
"@prisma/client": "^7.8.0",
|
||||
"@tailwindcss/vite": "^4.3.2",
|
||||
"@tanstack/react-devtools": "^0.10.8",
|
||||
"@tanstack/react-form": "^1.33.0",
|
||||
"@tanstack/react-query": "^5.101.2",
|
||||
"@tanstack/react-query-devtools": "^5.101.2",
|
||||
"@tanstack/react-router": "^1.170.17",
|
||||
"@tanstack/react-router-devtools": "^1.167.0",
|
||||
"@tanstack/react-router-ssr-query": "^1.167.1",
|
||||
"@tanstack/react-start": "^1.168.27",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@tanstack/router-plugin": "^1.132.0",
|
||||
"better-auth": "^1.4.10",
|
||||
"@tanstack/router-plugin": "^1.168.19",
|
||||
"better-auth": "^1.6.23",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"html-react-parser": "^5.2.16",
|
||||
"html-react-parser": "^6.1.3",
|
||||
"next-themes": "^0.4.6",
|
||||
"pnpm": "^10.30.2",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"shadcn": "^3.6.1",
|
||||
"pnpm": "^11.9.0",
|
||||
"radix-ui": "^1.6.1",
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7",
|
||||
"shadcn": "^4.12.0",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
"tailwindcss": "^4.0.6",
|
||||
"tw-animate-css": "^1.3.6",
|
||||
"vite-tsconfig-paths": "^6.0.5",
|
||||
"zod": "^4.3.6",
|
||||
"zustand": "^5.0.11"
|
||||
"tailwind-merge": "^3.6.0",
|
||||
"tailwindcss": "^4.3.2",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"zod": "^4.4.3",
|
||||
"zustand": "^5.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@inlang/paraglide-js": "2.10.0",
|
||||
"@tanstack/devtools-vite": "^0.5.0",
|
||||
"@tanstack/eslint-config": "^0.3.0",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@types/node": "^25.2.0",
|
||||
"@types/react": "^19.2.0",
|
||||
"@types/react-dom": "^19.2.0",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"@inlang/paraglide-js": "2.20.2",
|
||||
"@tanstack/devtools-vite": "^0.8.1",
|
||||
"@tanstack/eslint-config": "^0.4.0",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@types/node": "^26.1.0",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.3",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"jsdom": "^28.0.0",
|
||||
"prettier": "^3.8.1",
|
||||
"prisma": "^7.4.1",
|
||||
"tsx": "^4.20.6",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^7.1.7",
|
||||
"vitest": "^4.0.18",
|
||||
"web-vitals": "^5.1.0"
|
||||
"jsdom": "^29.1.1",
|
||||
"prettier": "^3.9.4",
|
||||
"prisma": "^7.8.0",
|
||||
"tsx": "^4.22.5",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.1.3",
|
||||
"vitest": "^4.1.9",
|
||||
"web-vitals": "^5.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
5968
pnpm-lock.yaml
generated
5968
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -61,13 +61,18 @@ const BoxDisplay = <TData, TValue>({
|
||||
key={row.id}
|
||||
>
|
||||
<div className="py-4 px-5 [&_svg]:w-full [&_svg]:h-full">
|
||||
<BoxIcon />
|
||||
<BoxIcon
|
||||
color={(row.original as BoxWithCount).color || '#F09C14'}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
{row.getVisibleCells().map((cell) => {
|
||||
if (cell.column.columnDef.id === 'actions') {
|
||||
return (
|
||||
<div className="bg-gray-200 py-1 px-3 justify-center">
|
||||
<div
|
||||
className="bg-gray-200 py-1 px-3 justify-center"
|
||||
key={cell.id}
|
||||
>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
@@ -78,6 +83,7 @@ const BoxDisplay = <TData, TValue>({
|
||||
return (
|
||||
<div
|
||||
data-slot={cell.column.columnDef.id}
|
||||
key={cell.id}
|
||||
className={cn('px-5 mb-2 text-center w-full', {
|
||||
'text-xs flex items-center justify-center gap-2':
|
||||
cell.column.columnDef.id !== 'name',
|
||||
@@ -89,16 +95,10 @@ const BoxDisplay = <TData, TValue>({
|
||||
<TagIcon size={15} className="inline-block mr-2" />
|
||||
)}
|
||||
{cell.column.columnDef.id === 'quantity' && (
|
||||
<CirclesThreeIcon
|
||||
size={15}
|
||||
className="inline-block mr-2"
|
||||
/>
|
||||
<CirclesThreeIcon size={15} />
|
||||
)}
|
||||
{cell.column.columnDef.id === 'time' && (
|
||||
<HourglassIcon
|
||||
size={15}
|
||||
className="inline-block mr-2"
|
||||
/>
|
||||
<HourglassIcon size={15} />
|
||||
)}
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { m } from '@/paraglide/messages';
|
||||
import { formatters } from '@/utils/formatters';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import EditBoxDialog from './edit-box-dialog';
|
||||
import ViewDetailBoxAction from './view-detail-dialog';
|
||||
|
||||
export const boxColumns: ColumnDef<BoxWithCount>[] = [
|
||||
@@ -40,8 +41,9 @@ export const boxColumns: ColumnDef<BoxWithCount>[] = [
|
||||
thClass: 'w-1/6',
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<div className="flex justify-end w-full">
|
||||
<div className="flex justify-center w-full">
|
||||
<ViewDetailBoxAction data={row.original} />
|
||||
<EditBoxDialog data={row.original} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
const BoxIcon = () => {
|
||||
const BoxIcon = ({ color = '#F09C14' }: { color?: string }) => {
|
||||
return (
|
||||
<svg viewBox="0 0 101 108" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="box">
|
||||
<path
|
||||
d="M50.7094 106.914L98.92 79.0797L98.9199 30.5769L56.8966 54.8177L50.7095 58.3867L50.7093 58.3866L50.7094 106.914Z"
|
||||
fill="#F09C14"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M2.49883 79.0797L50.7094 106.914L50.7093 58.3866L44.5221 54.8176L2.49873 30.5767L2.49883 79.0797Z"
|
||||
fill="#F09C14"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M50.7096 106.914L98.92 79.0797L98.9201 30.577L56.8966 54.8177L50.7097 58.3868L50.7095 58.3867L50.7096 106.914Z"
|
||||
fill="black"
|
||||
fill-opacity="0.1"
|
||||
fillOpacity="0.1"
|
||||
/>
|
||||
<path
|
||||
d="M50.7093 2.7423L2.49873 30.5767L44.5221 54.8176L50.7093 51.2454V2.7423Z"
|
||||
fill="#F09C14"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M50.7093 58.3866L50.7095 58.3867L56.8966 54.8177L50.7093 51.2454L44.5221 54.8176L50.7093 58.3866Z"
|
||||
fill="#F09C14"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M50.7093 2.7423V51.2454L56.8966 54.8177L98.9199 30.5769L50.7093 2.7423Z"
|
||||
fill="#F09C14"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M50.7093 2.7423V51.2454L56.8966 54.8177L98.9199 30.5769L50.7093 2.7423Z"
|
||||
fill="black"
|
||||
fill-opacity="0.2"
|
||||
fillOpacity="0.2"
|
||||
/>
|
||||
<path
|
||||
d="M50.7091 58.3865L50.7093 58.3866L56.8964 54.8176L50.7091 51.2453L44.5219 54.8175L50.7091 58.3865Z"
|
||||
fill="black"
|
||||
fill-opacity="0.3"
|
||||
fillOpacity="0.3"
|
||||
/>
|
||||
<path
|
||||
d="M50.7091 2.74219L2.49854 30.5765L44.5219 54.8175L50.7091 51.2453V2.74219Z"
|
||||
fill="black"
|
||||
fill-opacity="0.3"
|
||||
fillOpacity="0.3"
|
||||
/>
|
||||
<path
|
||||
d="M98.92 79.0797L98.9199 30.5769M98.92 79.0797L50.7096 106.914L50.7095 58.3867M98.92 79.0797L98.9201 30.577M98.92 79.0797L50.7094 106.914M50.7094 106.914L2.49883 79.0797L2.49873 30.5767M50.7094 106.914L50.7093 58.3866M50.7095 58.3867L50.7093 58.3866M50.7095 58.3867L50.7097 58.3868L56.8966 54.8177M50.7095 58.3867L56.8966 54.8177M98.9199 30.5769L56.8966 54.8177M98.9199 30.5769L50.7093 2.7423M2.49873 30.5767L50.7093 2.7423M2.49873 30.5767L44.5221 54.8176M50.7093 58.3866L44.5221 54.8176M50.7093 58.3866L50.7091 58.3865L44.5219 54.8175M50.7093 58.3866L56.8964 54.8176L50.7091 51.2453M98.9201 30.577L56.8966 54.8177M98.9201 30.577L50.7093 2.7423M50.7093 2.7423V51.2454M50.7093 51.2454L44.5221 54.8176M50.7093 51.2454L56.8966 54.8177M50.7091 51.2453L44.5219 54.8175M50.7091 51.2453V2.74219L2.49854 30.5765L44.5219 54.8175"
|
||||
@@ -50,15 +50,15 @@ const BoxIcon = () => {
|
||||
<g data-lid="close" className="group-hover:hidden">
|
||||
<path
|
||||
d="M100.5 29.4447L50.5 0.577148L0.5 29.4446L50.5 58.3122L100.5 29.4447Z"
|
||||
fill="#F09C14"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M100.5 37.8208V29.4447L50.5 58.3122V66.6629L100.5 37.8208Z"
|
||||
fill="#F09C14"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M50.5 66.6629V58.3122L0.5 29.4446V37.8208L50.4998 66.6628L50.5 66.6629Z"
|
||||
fill="#F09C14"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M50.5 66.6629V58.3122M50.5 66.6629L50.4998 66.6628L0.5 37.8208V29.4446M50.5 66.6629L100.5 37.8208V29.4447M0.5 29.4446L50.5 0.577148L100.5 29.4447M0.5 29.4446L50.5 58.3122M100.5 29.4447L50.5 58.3122"
|
||||
@@ -67,19 +67,19 @@ const BoxIcon = () => {
|
||||
</g>
|
||||
<g data-lid="open" className="hidden group-hover:block">
|
||||
<path
|
||||
d="M100.5 29.4159L50.5 0.54834L0.5 18.2581L50.5 47.1256L100.5 29.4159Z"
|
||||
fill="#F09C14"
|
||||
d="M100.5 29.3949L50.5003 0.527344L0.5 10.5273L50.5 39.3949L100.5 29.3949Z"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M100.5 37.7919V29.4159L50.5 47.1256V55.4763L100.5 37.7919Z"
|
||||
fill="#F09C14"
|
||||
d="M100.5 37.7709V29.3949L50.5 39.3949V47.7456L100.5 37.7709Z"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M50.5 55.4763V47.1256L0.5 18.2581V26.6342L50.5 55.4763Z"
|
||||
fill="#F09C14"
|
||||
d="M50.5 47.7456V39.3949L0.5 10.5273V18.9035L50.5 47.7456Z"
|
||||
fill={color}
|
||||
/>
|
||||
<path
|
||||
d="M50.5 55.4763V47.1256M50.5 55.4763L0.5 26.6342V18.2581M50.5 55.4763L100.5 37.7919V29.4159M0.5 18.2581L50.5 0.54834L100.5 29.4159M0.5 18.2581L50.5 47.1256M100.5 29.4159L50.5 47.1256"
|
||||
d="M50.5 47.7456V39.3949M50.5 47.7456L0.5 18.9035V10.5273M50.5 47.7456L100.5 37.7709V29.3949M0.5 10.5273L50.5003 0.527344L100.5 29.3949M0.5 10.5273L50.5 39.3949M100.5 29.3949L50.5 39.3949"
|
||||
stroke="black"
|
||||
/>
|
||||
</g>
|
||||
|
||||
@@ -53,7 +53,7 @@ const CreateBoxAction = ({ className }: CreateNewBoxProps) => {
|
||||
{m.nav_add_new()}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<CreateNewBoxForm />
|
||||
<CreateNewBoxForm onSubmit={_setOpen} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
66
src/components/boxes/edit-box-dialog.tsx
Normal file
66
src/components/boxes/edit-box-dialog.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import usePreventAutoFocus from '@/hooks/use-prevent-auto-focus';
|
||||
import { m } from '@/paraglide/messages';
|
||||
import { PenIcon } from '@phosphor-icons/react';
|
||||
import { Button } from '@ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@ui/dialog';
|
||||
import { Label } from '@ui/label';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@ui/tooltip';
|
||||
import { useState } from 'react';
|
||||
import AdminEditBoxForm from '../form/box/admin-edit-box-form';
|
||||
|
||||
type EditBoxProps = {
|
||||
data: BoxWithCount;
|
||||
};
|
||||
|
||||
const EditBoxDialog = ({ data }: EditBoxProps) => {
|
||||
const [_open, _setOpen] = useState(false);
|
||||
const prevent = usePreventAutoFocus();
|
||||
|
||||
return (
|
||||
<Dialog open={_open} onOpenChange={_setOpen}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="rounded-full cursor-pointer text-blue-500 hover:bg-blue-100 hover:text-blue-600"
|
||||
>
|
||||
<PenIcon size={16} />
|
||||
<span className="sr-only">{m.ui_edit_house_btn()}</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="bg-blue-500 [&_svg]:bg-blue-500 [&_svg]:fill-blue-500 text-white">
|
||||
<Label>{m.ui_edit_house_btn()}</Label>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<DialogContent
|
||||
className="max-w-100 xl:max-w-2xl"
|
||||
{...prevent}
|
||||
onPointerDownOutside={(e) => e.preventDefault()}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-3 text-lg font-bold text-blue-600">
|
||||
<PenIcon size={16} />
|
||||
{m.ui_edit_box_btn()}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
{m.ui_edit_box_btn()}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<AdminEditBoxForm data={data} onSubmit={_setOpen} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditBoxDialog;
|
||||
@@ -48,17 +48,14 @@ const ViewDetailBoxAction = ({ data }: ActionProps) => {
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="rounded-full cursor-pointer text-blue-500 hover:bg-blue-100 hover:text-blue-600"
|
||||
className="rounded-full cursor-pointer text-teal-500 hover:bg-teal-100 hover:text-teal-600"
|
||||
>
|
||||
<EyeIcon size={16} />
|
||||
<span className="sr-only">{m.ui_view_btn()}</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
side="left"
|
||||
className="bg-blue-500 [&_svg]:bg-blue-500 [&_svg]:fill-blue-500 text-white"
|
||||
>
|
||||
<TooltipContent className="bg-teal-500 [&_svg]:bg-teal-500 [&_svg]:fill-teal-500 text-white">
|
||||
<Label>{m.ui_view_btn()}</Label>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -68,7 +65,7 @@ const ViewDetailBoxAction = ({ data }: ActionProps) => {
|
||||
onPointerDownOutside={(e) => e.preventDefault()}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-3 text-lg font-bold text-blue-600">
|
||||
<DialogTitle className="flex items-center gap-3 text-lg font-bold text-teal-600">
|
||||
<EyeIcon size={20} />
|
||||
{m.ui_dialog_view_title({ type: m.nav_boxes() })}
|
||||
</DialogTitle>
|
||||
@@ -106,7 +103,7 @@ const ViewDetailBoxAction = ({ data }: ActionProps) => {
|
||||
></div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{data.tags.length && (
|
||||
{!!data.tags.length && (
|
||||
<TableRow>
|
||||
<TableCell>{m.boxes_page_ui_table_header_tags()}</TableCell>
|
||||
<TableCell className="flex">
|
||||
|
||||
82
src/components/form/box/admin-edit-box-form.tsx
Normal file
82
src/components/form/box/admin-edit-box-form.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { useAppForm } from '@/hooks/use-app-form';
|
||||
import { m } from '@/paraglide/messages';
|
||||
import { updateBoxSchema, updateBoxSchemaType } from '@service/box.schema';
|
||||
import { Button } from '@ui/button';
|
||||
import { DialogClose, DialogFooter } from '@ui/dialog';
|
||||
import { Field, FieldGroup } from '@ui/field';
|
||||
|
||||
type EditBoxForAdminProps = {
|
||||
data: BoxWithCount;
|
||||
onSubmit: (open: boolean) => void;
|
||||
};
|
||||
|
||||
const AdminEditBoxForm = ({ data, onSubmit }: EditBoxForAdminProps) => {
|
||||
const form = useAppForm({
|
||||
defaultValues: {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
color: data.color || '#000000',
|
||||
tags: data.tags || [],
|
||||
} as updateBoxSchemaType,
|
||||
validators: {
|
||||
onSubmit: updateBoxSchema,
|
||||
onChange: updateBoxSchema,
|
||||
},
|
||||
onSubmit: ({ value }) => {
|
||||
console.log(value);
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
id="admin-edit-box-form"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
form.handleSubmit();
|
||||
}}
|
||||
>
|
||||
<FieldGroup>
|
||||
<form.AppField name="name">
|
||||
{(field) => <field.TextField label={m.boxes_page_form_name()} />}
|
||||
</form.AppField>
|
||||
<form.AppField name="description">
|
||||
{(field) => (
|
||||
<field.TextArea label={m.boxes_page_form_description()} />
|
||||
)}
|
||||
</form.AppField>
|
||||
<form.AppField name="color">
|
||||
{(field) => (
|
||||
<field.TextField type="color" label={m.boxes_page_form_color()} />
|
||||
)}
|
||||
</form.AppField>
|
||||
<form.AppField name="tags">
|
||||
{(field) => (
|
||||
<field.TagInput
|
||||
label={m.boxes_page_form_tag()}
|
||||
placeholder={m.boxes_page_form_tag_placeholder()}
|
||||
/>
|
||||
)}
|
||||
</form.AppField>
|
||||
<Field>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline" type="button">
|
||||
{m.ui_cancel_btn()}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<form.AppForm>
|
||||
<form.SubscribeButton
|
||||
label={m.ui_confirm_btn()}
|
||||
// disabled={isPending}
|
||||
/>
|
||||
</form.AppForm>
|
||||
</DialogFooter>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminEditBoxForm;
|
||||
@@ -38,7 +38,7 @@ const CreateNewBoxForm = ({ onSubmit }: CreateBoxProp) => {
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate: createBoxMutation } = useMutation({
|
||||
const { mutate: createBoxMutation, isPending } = useMutation({
|
||||
mutationFn: createBox,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
@@ -124,7 +124,7 @@ const CreateNewBoxForm = ({ onSubmit }: CreateBoxProp) => {
|
||||
<form.AppForm>
|
||||
<form.SubscribeButton
|
||||
label={m.ui_confirm_btn()}
|
||||
// disabled={isPending}
|
||||
disabled={isPending}
|
||||
/>
|
||||
</form.AppForm>
|
||||
</DialogFooter>
|
||||
|
||||
7
src/components/form/item/admin-edit-item-form.tsx
Normal file
7
src/components/form/item/admin-edit-item-form.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
type EditItemForAdminProps = {};
|
||||
|
||||
const AdminEditItemForm = () => {
|
||||
return <div>AdminEditItemForm</div>;
|
||||
};
|
||||
|
||||
export default AdminEditItemForm;
|
||||
@@ -1,14 +1,25 @@
|
||||
import { m } from '@/paraglide/messages';
|
||||
import { CarrotIcon } from '@phosphor-icons/react';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { Card, CardHeader, CardTitle } from '@ui/card';
|
||||
|
||||
export const Route = createFileRoute('/(app)/(auth)/kanri/items')({
|
||||
component: RouteComponent,
|
||||
staticData: { breadcrumb: () => m.nav_items() },
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
return (
|
||||
<div className="@container/main flex flex-1 flex-col gap-2 p-4">
|
||||
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card *:data-[slot=card]:bg-linear-to-br *:data-[slot=card]:shadow-xs">
|
||||
Hello "/(app)/(auth)/kanri/items"!
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl flex items-center gap-2">
|
||||
<CarrotIcon size={24} />
|
||||
{m.items_page_ui_title()}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ import { auditListSchema } from './audit.schema';
|
||||
|
||||
export const getAllAudit = createServerFn({ method: 'GET' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(auditListSchema)
|
||||
.validator(auditListSchema)
|
||||
.handler(async ({ data }) => {
|
||||
try {
|
||||
const skip = (data.page - 1) * data.limit;
|
||||
|
||||
@@ -4,12 +4,12 @@ import { parseError } from '@/lib/errors';
|
||||
import { DB_TABLE, LOG_ACTION } from '@/types/enum';
|
||||
import { authMiddleware } from '@lib/middleware';
|
||||
import { createServerFn } from '@tanstack/react-start';
|
||||
import { boxListSchema, createBoxSchema } from './box.schema';
|
||||
import { boxListSchema, createBoxSchema, updateBoxSchema } from './box.schema';
|
||||
import { createAuditLog } from './repository';
|
||||
|
||||
export const getAllBox = createServerFn({ method: 'GET' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(boxListSchema)
|
||||
.validator(boxListSchema)
|
||||
.handler(async ({ data }) => {
|
||||
try {
|
||||
const { page, limit, keyword } = data;
|
||||
@@ -82,7 +82,7 @@ export const getAllBox = createServerFn({ method: 'GET' })
|
||||
|
||||
export const createBox = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(createBoxSchema)
|
||||
.validator(createBoxSchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const { name, description, color, tags, houseId } = data;
|
||||
@@ -116,3 +116,16 @@ export const createBox = createServerFn({ method: 'POST' })
|
||||
throw { message, code };
|
||||
}
|
||||
});
|
||||
|
||||
export const updateBoxAdmin = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.validator(updateBoxSchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const { id, name, description, color, tags } = data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const { message, code } = parseError(error);
|
||||
throw { message, code };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -24,3 +24,9 @@ export const createBoxSchema = z.object({
|
||||
houseId: z.string().nonempty(m.houses_page_message_house_not_found()),
|
||||
tags: z.array(z.string()),
|
||||
});
|
||||
|
||||
export const updateBoxSchema = baseBox.and(
|
||||
createBoxSchema.omit({ houseId: true }),
|
||||
);
|
||||
|
||||
export type updateBoxSchemaType = z.input<typeof updateBoxSchema>;
|
||||
|
||||
@@ -20,7 +20,7 @@ import { createAuditLog, createNotification } from './repository';
|
||||
|
||||
export const getAllHouse = createServerFn({ method: 'GET' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(houseListSchema)
|
||||
.validator(houseListSchema)
|
||||
.handler(async ({ data }) => {
|
||||
try {
|
||||
const { page, limit, keyword } = data;
|
||||
@@ -118,7 +118,7 @@ export const getCurrentUserHouses = createServerFn({ method: 'GET' })
|
||||
|
||||
export const getHouseForSelect = createServerFn({ method: 'GET' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(houseForSelectSchema)
|
||||
.validator(houseForSelectSchema)
|
||||
.handler(async ({ data }) => {
|
||||
try {
|
||||
const result = await prisma.house.findMany({
|
||||
@@ -151,7 +151,7 @@ export const getHouseForSelect = createServerFn({ method: 'GET' })
|
||||
|
||||
export const createHouse = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(houseCreateBESchema)
|
||||
.validator(houseCreateBESchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const result = await auth.api.createOrganization({
|
||||
@@ -179,7 +179,7 @@ export const createHouse = createServerFn({ method: 'POST' })
|
||||
|
||||
export const updateHouse = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(houseEditBESchema)
|
||||
.validator(houseEditBESchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const currentHouse = await prisma.house.findUnique({
|
||||
@@ -216,7 +216,7 @@ export const updateHouse = createServerFn({ method: 'POST' })
|
||||
|
||||
export const deleteHouse = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(baseHouse)
|
||||
.validator(baseHouse)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const currentHouse = await prisma.house.findUnique({
|
||||
@@ -255,7 +255,7 @@ export const deleteHouse = createServerFn({ method: 'POST' })
|
||||
|
||||
export const deleteUserHouse = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(baseHouse)
|
||||
.validator(baseHouse)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const currentHouse = await prisma.house.findUnique({
|
||||
@@ -292,7 +292,7 @@ export const deleteUserHouse = createServerFn({ method: 'POST' })
|
||||
|
||||
export const invitationMember = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(invitationCreateBESchema)
|
||||
.validator(invitationCreateBESchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const headers = getRequestHeaders();
|
||||
@@ -347,7 +347,7 @@ export const invitationMember = createServerFn({ method: 'POST' })
|
||||
|
||||
export const cancelInvitation = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(baseHouse)
|
||||
.validator(baseHouse)
|
||||
.handler(async ({ data }) => {
|
||||
try {
|
||||
const headers = getRequestHeaders();
|
||||
@@ -381,7 +381,7 @@ export const cancelInvitation = createServerFn({ method: 'POST' })
|
||||
|
||||
export const acceptInvitation = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(actionInvitationSchema)
|
||||
.validator(actionInvitationSchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const result = await prisma.invitation.update({
|
||||
@@ -434,7 +434,7 @@ export const acceptInvitation = createServerFn({ method: 'POST' })
|
||||
|
||||
export const rejectInvitation = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(actionInvitationSchema)
|
||||
.validator(actionInvitationSchema)
|
||||
.handler(async ({ data }) => {
|
||||
try {
|
||||
const headers = getRequestHeaders();
|
||||
@@ -462,7 +462,7 @@ export const rejectInvitation = createServerFn({ method: 'POST' })
|
||||
|
||||
export const leaveHouse = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(baseHouse)
|
||||
.validator(baseHouse)
|
||||
.handler(async ({ data }) => {
|
||||
try {
|
||||
const headers = getRequestHeaders();
|
||||
@@ -494,7 +494,7 @@ export const leaveHouse = createServerFn({ method: 'POST' })
|
||||
|
||||
export const removeMember = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(removeMemberSchema)
|
||||
.validator(removeMemberSchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const headers = getRequestHeaders();
|
||||
|
||||
@@ -42,7 +42,7 @@ export const getTopFiveNotification = createServerFn({ method: 'GET' })
|
||||
|
||||
export const getAllNotifications = createServerFn({ method: 'GET' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(notificationListSchema)
|
||||
.validator(notificationListSchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const skip = (data.page - 1) * data.limit;
|
||||
|
||||
@@ -12,7 +12,7 @@ import { changePasswordBESchema } from './user.schema';
|
||||
|
||||
export const updateProfile = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(z.instanceof(FormData))
|
||||
.validator(z.instanceof(FormData))
|
||||
.handler(async ({ data: formData, context: { user } }) => {
|
||||
try {
|
||||
let imageKey;
|
||||
@@ -61,7 +61,7 @@ export const updateProfile = createServerFn({ method: 'POST' })
|
||||
|
||||
export const changePassword = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(changePasswordBESchema)
|
||||
.validator(changePasswordBESchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const headers = getRequestHeaders();
|
||||
|
||||
@@ -33,7 +33,7 @@ export const getCurrentUserLanguage = createServerFn({ method: 'GET' })
|
||||
});
|
||||
|
||||
export const updateAdminSettings = createServerFn({ method: 'POST' })
|
||||
.inputValidator(settingSchema)
|
||||
.validator(settingSchema)
|
||||
.middleware([authMiddleware])
|
||||
.handler(async ({ data, context }) => {
|
||||
try {
|
||||
@@ -112,7 +112,7 @@ export const getUserSettings = createServerFn({ method: 'GET' })
|
||||
});
|
||||
|
||||
export const updateUserSettings = createServerFn({ method: 'POST' })
|
||||
.inputValidator(userSettingSchema)
|
||||
.validator(userSettingSchema)
|
||||
.middleware([authMiddleware])
|
||||
.handler(async ({ data, context }) => {
|
||||
// Update each setting
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
|
||||
export const getAllUser = createServerFn({ method: 'GET' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(userListSchema)
|
||||
.validator(userListSchema)
|
||||
.handler(async ({ data }) => {
|
||||
try {
|
||||
const headers = getRequestHeaders();
|
||||
@@ -58,7 +58,7 @@ export const getAllUser = createServerFn({ method: 'GET' })
|
||||
|
||||
export const getUserForSelect = createServerFn({ method: 'GET' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(userForSelectSchema)
|
||||
.validator(userForSelectSchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const { keyword, noself } = data;
|
||||
@@ -106,7 +106,7 @@ export const getUserForSelect = createServerFn({ method: 'GET' })
|
||||
|
||||
export const setUserPassword = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(userSetPasswordSchema)
|
||||
.validator(userSetPasswordSchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const headers = getRequestHeaders();
|
||||
@@ -137,7 +137,7 @@ export const setUserPassword = createServerFn({ method: 'POST' })
|
||||
|
||||
export const updateUserInformation = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(userUpdateInfoSchema)
|
||||
.validator(userUpdateInfoSchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
@@ -176,7 +176,7 @@ export const updateUserInformation = createServerFn({ method: 'POST' })
|
||||
|
||||
export const setUserRole = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(userUpdateRoleBESchema)
|
||||
.validator(userUpdateRoleBESchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const currentUser = await prisma.user.findUnique({
|
||||
@@ -215,7 +215,7 @@ export const setUserRole = createServerFn({ method: 'POST' })
|
||||
|
||||
export const banUser = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(userBanSchema)
|
||||
.validator(userBanSchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const headers = getRequestHeaders();
|
||||
@@ -251,7 +251,7 @@ export const banUser = createServerFn({ method: 'POST' })
|
||||
|
||||
export const unbanUser = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(baseUser)
|
||||
.validator(baseUser)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const headers = getRequestHeaders();
|
||||
@@ -282,7 +282,7 @@ export const unbanUser = createServerFn({ method: 'POST' })
|
||||
|
||||
export const createUser = createServerFn({ method: 'POST' })
|
||||
.middleware([authMiddleware])
|
||||
.inputValidator(userCreateBESchema)
|
||||
.validator(userCreateBESchema)
|
||||
.handler(async ({ data, context: { user } }) => {
|
||||
try {
|
||||
const result = await auth.api.createUser({
|
||||
|
||||
@@ -4,12 +4,14 @@ import { devtools } from '@tanstack/devtools-vite';
|
||||
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
|
||||
import viteReact from '@vitejs/plugin-react';
|
||||
import { defineConfig } from 'vite';
|
||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
||||
|
||||
const config = defineConfig({
|
||||
// server: {
|
||||
// host: true,
|
||||
// },
|
||||
resolve: {
|
||||
tsconfigPaths: true,
|
||||
},
|
||||
plugins: [
|
||||
paraglideVitePlugin({
|
||||
project: './project.inlang',
|
||||
@@ -19,10 +21,6 @@ const config = defineConfig({
|
||||
strategy: ['cookie', 'baseLocale'],
|
||||
}),
|
||||
devtools(),
|
||||
// this is the plugin that enables path aliases
|
||||
viteTsConfigPaths({
|
||||
projects: ['./tsconfig.json'],
|
||||
}),
|
||||
tailwindcss(),
|
||||
tanstackStart(),
|
||||
viteReact(),
|
||||
|
||||
Reference in New Issue
Block a user