diff --git a/messages/en.json b/messages/en.json index d10d513..cf736a7 100644 --- a/messages/en.json +++ b/messages/en.json @@ -56,6 +56,8 @@ "ui_view_all_notifications": "View All Notifications", "ui_label_notifications": "Notifications", "ui_change_password_btn": "Change password", + "nav_label_management": "Management", + "nav_label_basic": "Basic", "nav_home": "Home", "nav_dashboard": "Dashboard", "nav_settings": "Settings", @@ -63,8 +65,8 @@ "nav_edit": "Edit", "nav_change_password": "Change password", "nav_logs": "Logs", - "nav_users": "Người dùng", - "nav_roles": "Vai trò & quyền hạn", + "nav_users": "Users", + "nav_houses": "Houses", "nav_box": "Box", "nav_account": "Account", "nav_profile": "Profile", @@ -148,6 +150,13 @@ "users_page_ui_dialog_alert_ban_title": "", "users_page_ui_dialog_alert_description": "Detail: \nName: {name}. \nEmail: {email}", "users_page_ui_dialog_alert_description_2": "Reason: {reason}. \nExpiration: {exp}", + "houses_page_ui_title": "Houses", + "houses_page_ui_table_header_name": "Name", + "houses_page_ui_table_header_members": "Members", + "houses_page_ui_table_header_created_at": "Create date", + "houses_page_ui_view_label_count": "Quantity", + "houses_page_ui_view_table_header_email": "Email", + "houses_page_ui_view_table_header_role": "Quyền hạn", "backend_message": [ { "match": { diff --git a/messages/vi.json b/messages/vi.json index 4239ace..1f1bafa 100644 --- a/messages/vi.json +++ b/messages/vi.json @@ -52,10 +52,12 @@ "ui_update_password_btn": "Đặt lại mật khẩu", "ui_change_role_btn": "Đặt lại quyền hạn", "ui_edit_user_btn": "Chỉnh sửa người dùng", - "ui_dialog_view_title": "Xem chi tiết {type}", + "ui_dialog_view_title": "{type}: Xem chi tiết thông tin", "ui_view_all_notifications": "Xem tất cả thông báo", "ui_label_notifications": "Thông báo", "ui_change_password_btn": "Đổi mật khẩu", + "nav_label_management": "Quản lý", + "nav_label_basic": "Cơ bản", "nav_home": "Trang chủ", "nav_dashboard": "Bảng điều khiển", "nav_settings": "Cài đặt", @@ -64,7 +66,7 @@ "nav_change_password": "Đổi mật khẩu", "nav_logs": "Lịch sử", "nav_users": "Người dùng", - "nav_roles": "Vai trò & quyền hạn", + "nav_houses": "Nhà", "nav_box": "Hộp chứa", "nav_account": "Tài khoản", "nav_profile": "Hồ sơ", @@ -148,6 +150,13 @@ "users_page_ui_dialog_alert_ban_title": "Bạn muốn khóa người dùng này?", "users_page_ui_dialog_alert_description": "Chi tiết: \nTên: {name}. \nEmail: {email}", "users_page_ui_dialog_alert_description_2": "\nLý do: {reason}. \nHiệu lực: {exp}", + "houses_page_ui_title": "Nhà", + "houses_page_ui_table_header_name": "Tên", + "houses_page_ui_table_header_members": "Thành viên", + "houses_page_ui_table_header_created_at": "Ngày tạo", + "houses_page_ui_view_label_count": "Số lượng", + "houses_page_ui_view_table_header_email": "Email", + "houses_page_ui_view_table_header_role": "Quyền hạn", "backend_message": [ { "match": { diff --git a/src/components/DataTable.tsx b/src/components/DataTable.tsx index b078d6f..eedc70b 100644 --- a/src/components/DataTable.tsx +++ b/src/components/DataTable.tsx @@ -56,7 +56,7 @@ const DataTable = ({ return ( <>
- +
{table.getHeaderGroups().map((headerGroup) => ( @@ -66,7 +66,7 @@ const DataTable = ({ key={header.id} colSpan={header.colSpan} className={cn( - 'px-4', + 'px-4 bg-primary text-white text-sm', header.column.columnDef.meta?.thClass, )} > diff --git a/src/components/audit/audit-columns.tsx b/src/components/audit/audit-columns.tsx index 299991d..88291e5 100644 --- a/src/components/audit/audit-columns.tsx +++ b/src/components/audit/audit-columns.tsx @@ -5,7 +5,7 @@ import { Badge } from '../ui/badge'; import { LOG_ACTION } from '@/types/enum'; import ActionBadge from './action-badge'; -import ViewDetail from './view-detail-dialog'; +import ViewDetailAudit from './view-detail-dialog'; export const logColumns: ColumnDef[] = [ { @@ -56,7 +56,7 @@ export const logColumns: ColumnDef[] = [ }, cell: ({ row }) => (
- +
), }, diff --git a/src/components/audit/view-detail-dialog.tsx b/src/components/audit/view-detail-dialog.tsx index 7606448..b587549 100644 --- a/src/components/audit/view-detail-dialog.tsx +++ b/src/components/audit/view-detail-dialog.tsx @@ -23,7 +23,7 @@ type ViewDetailProps = { data: AuditWithUser; }; -const ViewDetail = ({ data }: ViewDetailProps) => { +const ViewDetailAudit = ({ data }: ViewDetailProps) => { const prevent = usePreventAutoFocus(); const { isCopied, copyToClipboard } = useCopyToClipboard(); @@ -134,4 +134,4 @@ const ViewDetail = ({ data }: ViewDetailProps) => { ); }; -export default ViewDetail; +export default ViewDetailAudit; diff --git a/src/components/form/profile-form.tsx b/src/components/form/profile-form.tsx index fdb8d7b..7f06556 100644 --- a/src/components/form/profile-form.tsx +++ b/src/components/form/profile-form.tsx @@ -74,6 +74,9 @@ const ProfileForm = () => { ); } catch (error) { console.error('update load file', error); + toast.error(JSON.stringify(error), { + richColors: true, + }); } }, }); diff --git a/src/components/house/create-house-dialog.tsx b/src/components/house/create-house-dialog.tsx new file mode 100644 index 0000000..71765ca --- /dev/null +++ b/src/components/house/create-house-dialog.tsx @@ -0,0 +1,46 @@ +import usePreventAutoFocus from '@/hooks/use-prevent-auto-focus'; +import { m } from '@/paraglide/messages'; +import { PlusIcon } from '@phosphor-icons/react'; +import { useState } from 'react'; +import { Button } from '../ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '../ui/dialog'; + +const CreateNewHouse = () => { + const [_open, _setOpen] = useState(false); + const prevent = usePreventAutoFocus(); + + return ( + + + + + e.preventDefault()} + > + + + + {m.nav_add_new()} + + + {m.nav_add_new()} + + + + + ); +}; + +export default CreateNewHouse; diff --git a/src/components/house/house-column.tsx b/src/components/house/house-column.tsx new file mode 100644 index 0000000..739b274 --- /dev/null +++ b/src/components/house/house-column.tsx @@ -0,0 +1,47 @@ +import { m } from '@/paraglide/messages'; +import { formatters } from '@/utils/formatters'; +import { ColumnDef } from '@tanstack/react-table'; +import ViewDetailHouse from './view-detail-dialog'; + +export const houseColumns: ColumnDef[] = [ + { + accessorKey: 'name', + header: m.houses_page_ui_table_header_name(), + meta: { + thClass: 'w-1/6', + }, + }, + { + accessorKey: 'members', + header: m.houses_page_ui_table_header_members(), + meta: { + thClass: 'w-1/6', + }, + cell: ({ row }) => { + return row.original.members.length; + }, + }, + { + accessorKey: 'createdAt', + header: m.houses_page_ui_table_header_created_at(), + meta: { + thClass: 'w-1/6', + }, + cell: ({ row }) => { + return formatters.dateTime(new Date(row.original.createdAt)); + }, + }, + { + id: 'actions', + meta: { + thClass: 'w-1/6', + }, + cell: ({ row }) => { + return ( +
+ +
+ ); + }, + }, +]; diff --git a/src/components/house/view-detail-dialog.tsx b/src/components/house/view-detail-dialog.tsx new file mode 100644 index 0000000..9498e94 --- /dev/null +++ b/src/components/house/view-detail-dialog.tsx @@ -0,0 +1,120 @@ +import usePreventAutoFocus from '@/hooks/use-prevent-auto-focus'; +import { m } from '@/paraglide/messages'; +import { formatters } from '@/utils/formatters'; +import { EyeIcon } 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 { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '../ui/table'; +import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; + +type ViewDetailProps = { + data: OrganizationWithMembers; +}; + +const ViewDetailHouse = ({ data }: ViewDetailProps) => { + const prevent = usePreventAutoFocus(); + + return ( + + + + + + + + + + + + e.preventDefault()} + > + + + + {m.ui_dialog_view_title({ type: m.nav_houses() })} + + + {m.ui_dialog_view_title({ type: m.nav_houses() })} + + +
+
+ + {m.houses_page_ui_table_header_name()}: + + +
+
+ + {m.houses_page_ui_table_header_created_at()}: + + +
+
+ + {m.houses_page_ui_table_header_members()}: + +
+ + {m.houses_page_ui_view_label_count()}: + + +
+
+
+ + + + {m.houses_page_ui_view_table_header_email()} + + + {m.houses_page_ui_view_table_header_role()} + + + + + {data.members.map((member) => ( + + {member.user.email} + {member.role} + + ))} + +
+
+ + + + + ); +}; +export default ViewDetailHouse; diff --git a/src/components/sidebar/nav-main.tsx b/src/components/sidebar/nav-main.tsx index 2bceb1e..c3cff00 100644 --- a/src/components/sidebar/nav-main.tsx +++ b/src/components/sidebar/nav-main.tsx @@ -5,6 +5,7 @@ import { GearIcon, HouseIcon, UsersIcon, + WarehouseIcon, } from '@phosphor-icons/react'; import { createLink } from '@tanstack/react-router'; import AdminShow from '../auth/AdminShow'; @@ -23,7 +24,7 @@ const SidebarMenuButtonLink = createLink(SidebarMenuButton); const NAV_MAIN = [ { id: '1', - title: 'Basic', + title: m.nav_label_basic(), items: [ { title: m.nav_home(), @@ -43,8 +44,15 @@ const NAV_MAIN = [ }, { id: '2', - title: 'Management', + title: m.nav_label_management(), items: [ + { + title: m.nav_houses(), + path: '/kanri/houses', + icon: WarehouseIcon, + isAuth: false, + admin: true, + }, { title: m.nav_users(), path: '/kanri/users', diff --git a/src/components/ui/search-input.tsx b/src/components/ui/search-input.tsx index ba6a540..c580b31 100644 --- a/src/components/ui/search-input.tsx +++ b/src/components/ui/search-input.tsx @@ -14,7 +14,7 @@ const SearchInput = ({ keywords, setKeyword, onChange }: SearchInputProps) => { }; return ( - + { onPointerDownOutside={(e) => e.preventDefault()} > - + {m.nav_add_new()} diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 13c5dbb..60ecea1 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -61,8 +61,8 @@ export const auth = betterAuth({ after: async (user) => { await auth.api.createOrganization({ body: { - name: `${user.name || 'User'}'s Organization`, - slug: `${user.name?.toLowerCase().replace(/\s+/g, '-')}-${Date.now()}`, + name: `${user.name || 'User'}'s House`, + slug: `${user.name?.toLowerCase().replace(/\s+/g, '-')}-house-${Date.now()}`, userId: user.id, color: '#000000', }, diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 57876cb..3377b64 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -22,6 +22,7 @@ import { Route as appauthAccountIndexRouteImport } from './routes/(app)/(auth)/a import { Route as appauthKanriUsersRouteImport } from './routes/(app)/(auth)/kanri/users' import { Route as appauthKanriSettingsRouteImport } from './routes/(app)/(auth)/kanri/settings' import { Route as appauthKanriLogsRouteImport } from './routes/(app)/(auth)/kanri/logs' +import { Route as appauthKanriHousesRouteImport } from './routes/(app)/(auth)/kanri/houses' import { Route as appauthAccountSettingsRouteImport } from './routes/(app)/(auth)/account/settings' import { Route as appauthAccountProfileRouteImport } from './routes/(app)/(auth)/account/profile' import { Route as appauthAccountChangePasswordRouteImport } from './routes/(app)/(auth)/account/change-password' @@ -89,6 +90,11 @@ const appauthKanriLogsRoute = appauthKanriLogsRouteImport.update({ path: '/logs', getParentRoute: () => appauthKanriRouteRoute, } as any) +const appauthKanriHousesRoute = appauthKanriHousesRouteImport.update({ + id: '/houses', + path: '/houses', + getParentRoute: () => appauthKanriRouteRoute, +} as any) const appauthAccountSettingsRoute = appauthAccountSettingsRouteImport.update({ id: '/settings', path: '/settings', @@ -116,6 +122,7 @@ export interface FileRoutesByFullPath { '/account/change-password': typeof appauthAccountChangePasswordRoute '/account/profile': typeof appauthAccountProfileRoute '/account/settings': typeof appauthAccountSettingsRoute + '/kanri/houses': typeof appauthKanriHousesRoute '/kanri/logs': typeof appauthKanriLogsRoute '/kanri/settings': typeof appauthKanriSettingsRoute '/kanri/users': typeof appauthKanriUsersRoute @@ -130,6 +137,7 @@ export interface FileRoutesByTo { '/account/change-password': typeof appauthAccountChangePasswordRoute '/account/profile': typeof appauthAccountProfileRoute '/account/settings': typeof appauthAccountSettingsRoute + '/kanri/houses': typeof appauthKanriHousesRoute '/kanri/logs': typeof appauthKanriLogsRoute '/kanri/settings': typeof appauthKanriSettingsRoute '/kanri/users': typeof appauthKanriUsersRoute @@ -149,6 +157,7 @@ export interface FileRoutesById { '/(app)/(auth)/account/change-password': typeof appauthAccountChangePasswordRoute '/(app)/(auth)/account/profile': typeof appauthAccountProfileRoute '/(app)/(auth)/account/settings': typeof appauthAccountSettingsRoute + '/(app)/(auth)/kanri/houses': typeof appauthKanriHousesRoute '/(app)/(auth)/kanri/logs': typeof appauthKanriLogsRoute '/(app)/(auth)/kanri/settings': typeof appauthKanriSettingsRoute '/(app)/(auth)/kanri/users': typeof appauthKanriUsersRoute @@ -167,6 +176,7 @@ export interface FileRouteTypes { | '/account/change-password' | '/account/profile' | '/account/settings' + | '/kanri/houses' | '/kanri/logs' | '/kanri/settings' | '/kanri/users' @@ -181,6 +191,7 @@ export interface FileRouteTypes { | '/account/change-password' | '/account/profile' | '/account/settings' + | '/kanri/houses' | '/kanri/logs' | '/kanri/settings' | '/kanri/users' @@ -199,6 +210,7 @@ export interface FileRouteTypes { | '/(app)/(auth)/account/change-password' | '/(app)/(auth)/account/profile' | '/(app)/(auth)/account/settings' + | '/(app)/(auth)/kanri/houses' | '/(app)/(auth)/kanri/logs' | '/(app)/(auth)/kanri/settings' | '/(app)/(auth)/kanri/users' @@ -305,6 +317,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof appauthKanriLogsRouteImport parentRoute: typeof appauthKanriRouteRoute } + '/(app)/(auth)/kanri/houses': { + id: '/(app)/(auth)/kanri/houses' + path: '/houses' + fullPath: '/kanri/houses' + preLoaderRoute: typeof appauthKanriHousesRouteImport + parentRoute: typeof appauthKanriRouteRoute + } '/(app)/(auth)/account/settings': { id: '/(app)/(auth)/account/settings' path: '/settings' @@ -347,6 +366,7 @@ const appauthAccountRouteRouteWithChildren = appauthAccountRouteRoute._addFileChildren(appauthAccountRouteRouteChildren) interface appauthKanriRouteRouteChildren { + appauthKanriHousesRoute: typeof appauthKanriHousesRoute appauthKanriLogsRoute: typeof appauthKanriLogsRoute appauthKanriSettingsRoute: typeof appauthKanriSettingsRoute appauthKanriUsersRoute: typeof appauthKanriUsersRoute @@ -354,6 +374,7 @@ interface appauthKanriRouteRouteChildren { } const appauthKanriRouteRouteChildren: appauthKanriRouteRouteChildren = { + appauthKanriHousesRoute: appauthKanriHousesRoute, appauthKanriLogsRoute: appauthKanriLogsRoute, appauthKanriSettingsRoute: appauthKanriSettingsRoute, appauthKanriUsersRoute: appauthKanriUsersRoute, diff --git a/src/routes/(app)/(auth)/kanri/houses.tsx b/src/routes/(app)/(auth)/kanri/houses.tsx new file mode 100644 index 0000000..8c06215 --- /dev/null +++ b/src/routes/(app)/(auth)/kanri/houses.tsx @@ -0,0 +1,81 @@ +import DataTable from '@/components/DataTable'; +import CreateNewHouse from '@/components/house/create-house-dialog'; +import { houseColumns } from '@/components/house/house-column'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import SearchInput from '@/components/ui/search-input'; +import { Skeleton } from '@/components/ui/skeleton'; +import useDebounced from '@/hooks/use-debounced'; +import { m } from '@/paraglide/messages'; +import { housesQueries } from '@/service/queries'; +import { WarehouseIcon } from '@phosphor-icons/react'; +import { useQuery } from '@tanstack/react-query'; +import { createFileRoute } from '@tanstack/react-router'; +import { useState } from 'react'; + +export const Route = createFileRoute('/(app)/(auth)/kanri/houses')({ + component: RouteComponent, +}); + +function RouteComponent() { + const [page, setPage] = useState(1); + const [pageLimit, setPageLimit] = useState(10); + const [searchKeyword, setSearchKeyword] = useState(''); + const debouncedSearch = useDebounced(searchKeyword, 500); + + const { data, isLoading } = useQuery( + housesQueries.list({ + page, + limit: pageLimit, + keyword: debouncedSearch, + }), + ); + + const onSearchChange = (e: React.ChangeEvent) => { + setSearchKeyword(e.target.value); + setPage(1); + }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + return ( +
+
+ + + + + {m.houses_page_ui_title()} + + + +
+ + +
+ {data && ( + + )} +
+
+
+
+ ); +} diff --git a/src/routes/(app)/(auth)/kanri/logs.tsx b/src/routes/(app)/(auth)/kanri/logs.tsx index fc29803..294a2ad 100644 --- a/src/routes/(app)/(auth)/kanri/logs.tsx +++ b/src/routes/(app)/(auth)/kanri/logs.tsx @@ -1,6 +1,6 @@ import { logColumns } from '@/components/audit/audit-columns'; import DataTable from '@/components/DataTable'; -import { Card, CardHeader, CardTitle } from '@/components/ui/card'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import SearchInput from '@/components/ui/search-input'; import { Skeleton } from '@/components/ui/skeleton'; import useDebounced from '@/hooks/use-debounced'; @@ -37,17 +37,14 @@ function RouteComponent() { if (isLoading) { return (
-
- - -
+
); } return (
-
+
@@ -55,25 +52,27 @@ function RouteComponent() { {m.logs_page_ui_title()} + +
+ +
+ {data?.result && ( + + )} +
-
- -
- {data?.result && ( - - )}
); diff --git a/src/routes/(app)/(auth)/kanri/users.tsx b/src/routes/(app)/(auth)/kanri/users.tsx index e1c5111..4e18c1d 100644 --- a/src/routes/(app)/(auth)/kanri/users.tsx +++ b/src/routes/(app)/(auth)/kanri/users.tsx @@ -1,5 +1,5 @@ import DataTable from '@/components/DataTable'; -import { Card, CardHeader, CardTitle } from '@/components/ui/card'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import SearchInput from '@/components/ui/search-input'; import { Skeleton } from '@/components/ui/skeleton'; import AddNewUserButton from '@/components/user/add-new-user-dialog'; @@ -39,44 +39,43 @@ function RouteComponent() { if (isLoading) { return (
-
- - -
+
); } return (
-
+
- + {m.users_page_ui_title()} + +
+ + +
+ {data && ( + + )} +
-
- - -
- {data && ( - - )}
); diff --git a/src/service/audit.api.ts b/src/service/audit.api.ts index 6e00cbb..6ddbbd6 100644 --- a/src/service/audit.api.ts +++ b/src/service/audit.api.ts @@ -1,6 +1,7 @@ import { prisma } from '@/db'; import { AuditWhereInput } from '@/generated/prisma/models'; import { authMiddleware } from '@/lib/middleware'; +import { parseError } from '@/utils/helper'; import { createServerFn } from '@tanstack/react-start'; import { auditListSchema } from './audit.schema'; @@ -62,7 +63,8 @@ export const getAllAudit = createServerFn({ method: 'GET' }) }, }; } catch (error) { - console.log(error); - throw error; + console.error(error); + const { message, code } = parseError(error); + throw { message, code }; } }); diff --git a/src/service/house.api.ts b/src/service/house.api.ts new file mode 100644 index 0000000..4532b93 --- /dev/null +++ b/src/service/house.api.ts @@ -0,0 +1,67 @@ +import { prisma } from '@/db'; +import { OrganizationWhereInput } from '@/generated/prisma/models'; +import { authMiddleware } from '@/lib/middleware'; +import { parseError } from '@/utils/helper'; +import { createServerFn } from '@tanstack/react-start'; +import { houseListSchema } from './house.schema'; + +export const getAllHouse = createServerFn({ method: 'GET' }) + .middleware([authMiddleware]) + .inputValidator(houseListSchema) + .handler(async ({ data }) => { + try { + const { page, limit, keyword } = data; + const skip = (page - 1) * limit; + + const where: OrganizationWhereInput = { + OR: [ + { + name: { + contains: keyword, + mode: 'insensitive', + }, + }, + ], + }; + + const [list, total]: [OrganizationWithMembers[], number] = + await Promise.all([ + await prisma.organization.findMany({ + where, + orderBy: { createdAt: 'desc' }, + include: { + members: { + select: { + role: true, + user: { + select: { + name: true, + email: true, + image: true, + }, + }, + }, + }, + }, + take: limit, + skip, + }), + await prisma.organization.count({ where }), + ]); + + const totalPage = Math.ceil(+total / limit); + + return { + result: list, + pagination: { + currentPage: page, + totalPage, + totalItem: total, + }, + }; + } catch (error) { + console.error(error); + const { message, code } = parseError(error); + throw { message, code }; + } + }); diff --git a/src/service/house.schema.ts b/src/service/house.schema.ts new file mode 100644 index 0000000..d052352 --- /dev/null +++ b/src/service/house.schema.ts @@ -0,0 +1,12 @@ +import { m } from '@/paraglide/messages'; +import z from 'zod'; + +export const baseHouse = z.object({ + id: z.string().nonempty(m.users_page_message_user_not_found()), +}); + +export const houseListSchema = z.object({ + page: z.coerce.number().min(1).default(1), + limit: z.coerce.number().min(10).max(100).default(10), + keyword: z.string().optional(), +}); diff --git a/src/service/queries.ts b/src/service/queries.ts index c8bbadd..3d9b4c7 100644 --- a/src/service/queries.ts +++ b/src/service/queries.ts @@ -1,6 +1,7 @@ import { getSession } from '@/lib/auth/session'; import { queryOptions } from '@tanstack/react-query'; import { getAllAudit } from './audit.api'; +import { getAllHouse } from './house.api'; import { getAdminSettings, getCurrentUserLanguage, @@ -55,3 +56,12 @@ export const usersQueries = { queryFn: () => getAllUser({ data: params }), }), }; + +export const housesQueries = { + all: ['houses'], + list: (params: { page: number; limit: number; keyword?: string }) => + queryOptions({ + queryKey: [...housesQueries.all, 'list', params], + queryFn: () => getAllHouse({ data: params }), + }), +}; diff --git a/src/service/repository.ts b/src/service/repository.ts index 3981060..3a4a16f 100644 --- a/src/service/repository.ts +++ b/src/service/repository.ts @@ -1,5 +1,6 @@ import { prisma } from '@/db'; import { Audit, Setting } from '@/generated/prisma/client'; +import { parseError } from '@/utils/helper'; type AdminSettingValue = Pick; @@ -48,7 +49,8 @@ export const createAuditLog = async (data: Omit) => { }, }); } catch (error) { - console.log(error); - throw error; + console.error(error); + const { message, code } = parseError(error); + throw { message, code }; } }; diff --git a/src/service/setting.api.ts b/src/service/setting.api.ts index 7636dcc..443b8ae 100644 --- a/src/service/setting.api.ts +++ b/src/service/setting.api.ts @@ -1,6 +1,6 @@ import { prisma } from '@/db'; import { authMiddleware } from '@/lib/middleware'; -import { extractDiffObjects } from '@/utils/helper'; +import { extractDiffObjects, parseError } from '@/utils/helper'; import { createServerFn } from '@tanstack/react-start'; import { createAuditLog, getAllAdminSettings } from './repository'; import { settingSchema, userSettingSchema } from './setting.schema'; @@ -25,8 +25,9 @@ export const getCurrentUserLanguage = createServerFn({ method: 'GET' }) return value.language; } catch (error) { - console.log(error); - throw error; + console.error(error); + const { message, code } = parseError(error); + throw { message, code }; } }); @@ -71,8 +72,9 @@ export const updateAdminSettings = createServerFn({ method: 'POST' }) return { success: true }; } catch (error) { - console.log(error); - throw error; + console.error(error); + const { message, code } = parseError(error); + throw { message, code }; } }); @@ -102,8 +104,9 @@ export const getUserSettings = createServerFn({ method: 'GET' }) value: JSON.parse(settings.value) as UserSetting, }; } catch (error) { - console.log(error); - throw error; + console.error(error); + const { message, code } = parseError(error); + throw { message, code }; } }); @@ -147,7 +150,8 @@ export const updateUserSettings = createServerFn({ method: 'POST' }) return { success: true }; } catch (error) { - console.log(error); - throw error; + console.error(error); + const { message, code } = parseError(error); + throw { message, code }; } }); diff --git a/src/service/user.api.ts b/src/service/user.api.ts index 2d9ca09..5117687 100644 --- a/src/service/user.api.ts +++ b/src/service/user.api.ts @@ -20,33 +20,39 @@ export const getAllUser = createServerFn({ method: 'GET' }) .middleware([authMiddleware]) .inputValidator(userListSchema) .handler(async ({ data }) => { - const headers = getRequestHeaders(); - const { page, limit, keyword } = data; + try { + const headers = getRequestHeaders(); + const { page, limit, keyword } = data; - const list = await auth.api.listUsers({ - query: { - searchValue: keyword, - searchField: 'name', - searchOperator: 'contains', - sortBy: 'createdAt', - sortDirection: 'asc', - limit, - offset: (page - 1) * limit, - }, - headers, - }); + const list = await auth.api.listUsers({ + query: { + searchValue: keyword, + searchField: 'name', + searchOperator: 'contains', + sortBy: 'createdAt', + sortDirection: 'asc', + limit, + offset: (page - 1) * limit, + }, + headers, + }); - const totalItem = list.total; - const totalPage = Math.ceil(totalItem / limit); + const totalItem = list.total; + const totalPage = Math.ceil(totalItem / limit); - return { - result: list.users, - pagination: { - currentPage: page, - totalPage, - totalItem, - }, - }; + return { + result: list.users, + pagination: { + currentPage: page, + totalPage, + totalItem, + }, + }; + } catch (error) { + console.error(error); + const { message, code } = parseError(error); + throw { message, code }; + } }); export const setUserPassword = createServerFn({ method: 'POST' }) @@ -74,6 +80,7 @@ export const setUserPassword = createServerFn({ method: 'POST' }) return result; } catch (error) { + console.error(error); const { message, code } = parseError(error); throw { message, code }; } @@ -112,6 +119,7 @@ export const updateUserInformation = createServerFn({ method: 'POST' }) return result; } catch (error) { + console.error(error); const { message, code } = parseError(error); throw { message, code }; } @@ -150,6 +158,7 @@ export const setUserRole = createServerFn({ method: 'POST' }) return result; } catch (error) { + console.error(error); const { message, code } = parseError(error); throw { message, code }; } @@ -185,6 +194,7 @@ export const banUser = createServerFn({ method: 'POST' }) return result; } catch (error) { + console.error(error); const { message, code } = parseError(error); throw { message, code }; } @@ -215,6 +225,7 @@ export const unbanUser = createServerFn({ method: 'POST' }) return result; } catch (error) { + console.error(error); const { message, code } = parseError(error); throw { message, code }; } diff --git a/src/types/db.d.ts b/src/types/db.d.ts index ef0beca..7f2ade1 100644 --- a/src/types/db.d.ts +++ b/src/types/db.d.ts @@ -11,4 +11,21 @@ declare global { }; }; }>; + + type OrganizationWithMembers = Prisma.OrganizationGetPayload<{ + include: { + members: { + select: { + role: true; + user: { + select: { + name: true; + email: true; + image: true; + }; + }; + }; + }; + }; + }>; }