feature/houses #11
@@ -56,6 +56,8 @@
|
|||||||
"ui_view_all_notifications": "View All Notifications",
|
"ui_view_all_notifications": "View All Notifications",
|
||||||
"ui_label_notifications": "Notifications",
|
"ui_label_notifications": "Notifications",
|
||||||
"ui_change_password_btn": "Change password",
|
"ui_change_password_btn": "Change password",
|
||||||
|
"nav_label_management": "Management",
|
||||||
|
"nav_label_basic": "Basic",
|
||||||
"nav_home": "Home",
|
"nav_home": "Home",
|
||||||
"nav_dashboard": "Dashboard",
|
"nav_dashboard": "Dashboard",
|
||||||
"nav_settings": "Settings",
|
"nav_settings": "Settings",
|
||||||
@@ -63,8 +65,8 @@
|
|||||||
"nav_edit": "Edit",
|
"nav_edit": "Edit",
|
||||||
"nav_change_password": "Change password",
|
"nav_change_password": "Change password",
|
||||||
"nav_logs": "Logs",
|
"nav_logs": "Logs",
|
||||||
"nav_users": "Người dùng",
|
"nav_users": "Users",
|
||||||
"nav_roles": "Vai trò & quyền hạn",
|
"nav_houses": "Houses",
|
||||||
"nav_box": "Box",
|
"nav_box": "Box",
|
||||||
"nav_account": "Account",
|
"nav_account": "Account",
|
||||||
"nav_profile": "Profile",
|
"nav_profile": "Profile",
|
||||||
@@ -148,6 +150,13 @@
|
|||||||
"users_page_ui_dialog_alert_ban_title": "",
|
"users_page_ui_dialog_alert_ban_title": "",
|
||||||
"users_page_ui_dialog_alert_description": "Detail: \nName: {name}. \nEmail: {email}",
|
"users_page_ui_dialog_alert_description": "Detail: \nName: {name}. \nEmail: {email}",
|
||||||
"users_page_ui_dialog_alert_description_2": "Reason: {reason}. \nExpiration: {exp}",
|
"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": [
|
"backend_message": [
|
||||||
{
|
{
|
||||||
"match": {
|
"match": {
|
||||||
|
|||||||
@@ -52,10 +52,12 @@
|
|||||||
"ui_update_password_btn": "Đặt lại mật khẩu",
|
"ui_update_password_btn": "Đặt lại mật khẩu",
|
||||||
"ui_change_role_btn": "Đặt lại quyền hạn",
|
"ui_change_role_btn": "Đặt lại quyền hạn",
|
||||||
"ui_edit_user_btn": "Chỉnh sửa người dùng",
|
"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_view_all_notifications": "Xem tất cả thông báo",
|
||||||
"ui_label_notifications": "Thông báo",
|
"ui_label_notifications": "Thông báo",
|
||||||
"ui_change_password_btn": "Đổi mật khẩu",
|
"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_home": "Trang chủ",
|
||||||
"nav_dashboard": "Bảng điều khiển",
|
"nav_dashboard": "Bảng điều khiển",
|
||||||
"nav_settings": "Cài đặt",
|
"nav_settings": "Cài đặt",
|
||||||
@@ -64,7 +66,7 @@
|
|||||||
"nav_change_password": "Đổi mật khẩu",
|
"nav_change_password": "Đổi mật khẩu",
|
||||||
"nav_logs": "Lịch sử",
|
"nav_logs": "Lịch sử",
|
||||||
"nav_users": "Người dùng",
|
"nav_users": "Người dùng",
|
||||||
"nav_roles": "Vai trò & quyền hạn",
|
"nav_houses": "Nhà",
|
||||||
"nav_box": "Hộp chứa",
|
"nav_box": "Hộp chứa",
|
||||||
"nav_account": "Tài khoản",
|
"nav_account": "Tài khoản",
|
||||||
"nav_profile": "Hồ sơ",
|
"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_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": "Chi tiết: \nTên: {name}. \nEmail: {email}",
|
||||||
"users_page_ui_dialog_alert_description_2": "\nLý do: {reason}. \nHiệu lực: {exp}",
|
"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": [
|
"backend_message": [
|
||||||
{
|
{
|
||||||
"match": {
|
"match": {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const DataTable = <TData, TValue>({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="overflow-hidden rounded-md border">
|
<div className="overflow-hidden rounded-md border">
|
||||||
<Table>
|
<Table className="bg-white">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id}>
|
<TableRow key={headerGroup.id}>
|
||||||
@@ -66,7 +66,7 @@ const DataTable = <TData, TValue>({
|
|||||||
key={header.id}
|
key={header.id}
|
||||||
colSpan={header.colSpan}
|
colSpan={header.colSpan}
|
||||||
className={cn(
|
className={cn(
|
||||||
'px-4',
|
'px-4 bg-primary text-white text-sm',
|
||||||
header.column.columnDef.meta?.thClass,
|
header.column.columnDef.meta?.thClass,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Badge } from '../ui/badge';
|
|||||||
|
|
||||||
import { LOG_ACTION } from '@/types/enum';
|
import { LOG_ACTION } from '@/types/enum';
|
||||||
import ActionBadge from './action-badge';
|
import ActionBadge from './action-badge';
|
||||||
import ViewDetail from './view-detail-dialog';
|
import ViewDetailAudit from './view-detail-dialog';
|
||||||
|
|
||||||
export const logColumns: ColumnDef<AuditWithUser>[] = [
|
export const logColumns: ColumnDef<AuditWithUser>[] = [
|
||||||
{
|
{
|
||||||
@@ -56,7 +56,7 @@ export const logColumns: ColumnDef<AuditWithUser>[] = [
|
|||||||
},
|
},
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<ViewDetail data={row.original} />
|
<ViewDetailAudit data={row.original} />
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ type ViewDetailProps = {
|
|||||||
data: AuditWithUser;
|
data: AuditWithUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ViewDetail = ({ data }: ViewDetailProps) => {
|
const ViewDetailAudit = ({ data }: ViewDetailProps) => {
|
||||||
const prevent = usePreventAutoFocus();
|
const prevent = usePreventAutoFocus();
|
||||||
const { isCopied, copyToClipboard } = useCopyToClipboard();
|
const { isCopied, copyToClipboard } = useCopyToClipboard();
|
||||||
|
|
||||||
@@ -134,4 +134,4 @@ const ViewDetail = ({ data }: ViewDetailProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ViewDetail;
|
export default ViewDetailAudit;
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ const ProfileForm = () => {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('update load file', error);
|
console.error('update load file', error);
|
||||||
|
toast.error(JSON.stringify(error), {
|
||||||
|
richColors: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
46
src/components/house/create-house-dialog.tsx
Normal file
46
src/components/house/create-house-dialog.tsx
Normal file
@@ -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 (
|
||||||
|
<Dialog open={_open} onOpenChange={_setOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button type="button" variant="default">
|
||||||
|
<PlusIcon />
|
||||||
|
{m.nav_add_new()}
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent
|
||||||
|
className="max-w-80 xl:max-w-xl"
|
||||||
|
{...prevent}
|
||||||
|
onPointerDownOutside={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-3 text-lg font-bold text-primary">
|
||||||
|
<PlusIcon size={16} />
|
||||||
|
{m.nav_add_new()}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription className="sr-only">
|
||||||
|
{m.nav_add_new()}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateNewHouse;
|
||||||
47
src/components/house/house-column.tsx
Normal file
47
src/components/house/house-column.tsx
Normal file
@@ -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<OrganizationWithMembers>[] = [
|
||||||
|
{
|
||||||
|
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 (
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<ViewDetailHouse data={row.original} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
120
src/components/house/view-detail-dialog.tsx
Normal file
120
src/components/house/view-detail-dialog.tsx
Normal file
@@ -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 (
|
||||||
|
<Dialog>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
<Label>{m.ui_view_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">
|
||||||
|
<EyeIcon size={20} />
|
||||||
|
{m.ui_dialog_view_title({ type: m.nav_houses() })}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription className="sr-only">
|
||||||
|
{m.ui_dialog_view_title({ type: m.nav_houses() })}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-bold">
|
||||||
|
{m.houses_page_ui_table_header_name()}:
|
||||||
|
</span>
|
||||||
|
<Label>{data.name}</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-bold">
|
||||||
|
{m.houses_page_ui_table_header_created_at()}:
|
||||||
|
</span>
|
||||||
|
<Label>{formatters.dateTime(new Date(data.createdAt))}</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<span className="font-bold">
|
||||||
|
{m.houses_page_ui_table_header_members()}:
|
||||||
|
</span>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-bold">
|
||||||
|
{m.houses_page_ui_view_label_count()}:
|
||||||
|
</span>
|
||||||
|
<Label>{data.members.length}</Label>
|
||||||
|
</div>
|
||||||
|
<div className="overflow-hidden rounded-md border">
|
||||||
|
<Table className="bg-white">
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead className="px-2 h-7 bg-primary text-white text-xs w-1/2">
|
||||||
|
{m.houses_page_ui_view_table_header_email()}
|
||||||
|
</TableHead>
|
||||||
|
<TableHead className="px-2 h-7 bg-primary text-white text-xs w-1/2">
|
||||||
|
{m.houses_page_ui_view_table_header_role()}
|
||||||
|
</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{data.members.map((member) => (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{member.user.email}</TableCell>
|
||||||
|
<TableCell>{member.role}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ViewDetailHouse;
|
||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
GearIcon,
|
GearIcon,
|
||||||
HouseIcon,
|
HouseIcon,
|
||||||
UsersIcon,
|
UsersIcon,
|
||||||
|
WarehouseIcon,
|
||||||
} from '@phosphor-icons/react';
|
} from '@phosphor-icons/react';
|
||||||
import { createLink } from '@tanstack/react-router';
|
import { createLink } from '@tanstack/react-router';
|
||||||
import AdminShow from '../auth/AdminShow';
|
import AdminShow from '../auth/AdminShow';
|
||||||
@@ -23,7 +24,7 @@ const SidebarMenuButtonLink = createLink(SidebarMenuButton);
|
|||||||
const NAV_MAIN = [
|
const NAV_MAIN = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
title: 'Basic',
|
title: m.nav_label_basic(),
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: m.nav_home(),
|
title: m.nav_home(),
|
||||||
@@ -43,8 +44,15 @@ const NAV_MAIN = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
title: 'Management',
|
title: m.nav_label_management(),
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
title: m.nav_houses(),
|
||||||
|
path: '/kanri/houses',
|
||||||
|
icon: WarehouseIcon,
|
||||||
|
isAuth: false,
|
||||||
|
admin: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: m.nav_users(),
|
title: m.nav_users(),
|
||||||
path: '/kanri/users',
|
path: '/kanri/users',
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const SearchInput = ({ keywords, setKeyword, onChange }: SearchInputProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputGroup className="w-70">
|
<InputGroup className="w-70 bg-white">
|
||||||
<InputGroupInput
|
<InputGroupInput
|
||||||
id="keywords"
|
id="keywords"
|
||||||
placeholder="Search...."
|
placeholder="Search...."
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const AddNewUserButton = () => {
|
|||||||
onPointerDownOutside={(e) => e.preventDefault()}
|
onPointerDownOutside={(e) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-3 text-lg font-bold text-red-600">
|
<DialogTitle className="flex items-center gap-3 text-lg font-bold text-primary">
|
||||||
<PlusIcon size={16} />
|
<PlusIcon size={16} />
|
||||||
{m.nav_add_new()}
|
{m.nav_add_new()}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ export const auth = betterAuth({
|
|||||||
after: async (user) => {
|
after: async (user) => {
|
||||||
await auth.api.createOrganization({
|
await auth.api.createOrganization({
|
||||||
body: {
|
body: {
|
||||||
name: `${user.name || 'User'}'s Organization`,
|
name: `${user.name || 'User'}'s House`,
|
||||||
slug: `${user.name?.toLowerCase().replace(/\s+/g, '-')}-${Date.now()}`,
|
slug: `${user.name?.toLowerCase().replace(/\s+/g, '-')}-house-${Date.now()}`,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
color: '#000000',
|
color: '#000000',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 appauthKanriUsersRouteImport } from './routes/(app)/(auth)/kanri/users'
|
||||||
import { Route as appauthKanriSettingsRouteImport } from './routes/(app)/(auth)/kanri/settings'
|
import { Route as appauthKanriSettingsRouteImport } from './routes/(app)/(auth)/kanri/settings'
|
||||||
import { Route as appauthKanriLogsRouteImport } from './routes/(app)/(auth)/kanri/logs'
|
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 appauthAccountSettingsRouteImport } from './routes/(app)/(auth)/account/settings'
|
||||||
import { Route as appauthAccountProfileRouteImport } from './routes/(app)/(auth)/account/profile'
|
import { Route as appauthAccountProfileRouteImport } from './routes/(app)/(auth)/account/profile'
|
||||||
import { Route as appauthAccountChangePasswordRouteImport } from './routes/(app)/(auth)/account/change-password'
|
import { Route as appauthAccountChangePasswordRouteImport } from './routes/(app)/(auth)/account/change-password'
|
||||||
@@ -89,6 +90,11 @@ const appauthKanriLogsRoute = appauthKanriLogsRouteImport.update({
|
|||||||
path: '/logs',
|
path: '/logs',
|
||||||
getParentRoute: () => appauthKanriRouteRoute,
|
getParentRoute: () => appauthKanriRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const appauthKanriHousesRoute = appauthKanriHousesRouteImport.update({
|
||||||
|
id: '/houses',
|
||||||
|
path: '/houses',
|
||||||
|
getParentRoute: () => appauthKanriRouteRoute,
|
||||||
|
} as any)
|
||||||
const appauthAccountSettingsRoute = appauthAccountSettingsRouteImport.update({
|
const appauthAccountSettingsRoute = appauthAccountSettingsRouteImport.update({
|
||||||
id: '/settings',
|
id: '/settings',
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
@@ -116,6 +122,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/account/change-password': typeof appauthAccountChangePasswordRoute
|
'/account/change-password': typeof appauthAccountChangePasswordRoute
|
||||||
'/account/profile': typeof appauthAccountProfileRoute
|
'/account/profile': typeof appauthAccountProfileRoute
|
||||||
'/account/settings': typeof appauthAccountSettingsRoute
|
'/account/settings': typeof appauthAccountSettingsRoute
|
||||||
|
'/kanri/houses': typeof appauthKanriHousesRoute
|
||||||
'/kanri/logs': typeof appauthKanriLogsRoute
|
'/kanri/logs': typeof appauthKanriLogsRoute
|
||||||
'/kanri/settings': typeof appauthKanriSettingsRoute
|
'/kanri/settings': typeof appauthKanriSettingsRoute
|
||||||
'/kanri/users': typeof appauthKanriUsersRoute
|
'/kanri/users': typeof appauthKanriUsersRoute
|
||||||
@@ -130,6 +137,7 @@ export interface FileRoutesByTo {
|
|||||||
'/account/change-password': typeof appauthAccountChangePasswordRoute
|
'/account/change-password': typeof appauthAccountChangePasswordRoute
|
||||||
'/account/profile': typeof appauthAccountProfileRoute
|
'/account/profile': typeof appauthAccountProfileRoute
|
||||||
'/account/settings': typeof appauthAccountSettingsRoute
|
'/account/settings': typeof appauthAccountSettingsRoute
|
||||||
|
'/kanri/houses': typeof appauthKanriHousesRoute
|
||||||
'/kanri/logs': typeof appauthKanriLogsRoute
|
'/kanri/logs': typeof appauthKanriLogsRoute
|
||||||
'/kanri/settings': typeof appauthKanriSettingsRoute
|
'/kanri/settings': typeof appauthKanriSettingsRoute
|
||||||
'/kanri/users': typeof appauthKanriUsersRoute
|
'/kanri/users': typeof appauthKanriUsersRoute
|
||||||
@@ -149,6 +157,7 @@ export interface FileRoutesById {
|
|||||||
'/(app)/(auth)/account/change-password': typeof appauthAccountChangePasswordRoute
|
'/(app)/(auth)/account/change-password': typeof appauthAccountChangePasswordRoute
|
||||||
'/(app)/(auth)/account/profile': typeof appauthAccountProfileRoute
|
'/(app)/(auth)/account/profile': typeof appauthAccountProfileRoute
|
||||||
'/(app)/(auth)/account/settings': typeof appauthAccountSettingsRoute
|
'/(app)/(auth)/account/settings': typeof appauthAccountSettingsRoute
|
||||||
|
'/(app)/(auth)/kanri/houses': typeof appauthKanriHousesRoute
|
||||||
'/(app)/(auth)/kanri/logs': typeof appauthKanriLogsRoute
|
'/(app)/(auth)/kanri/logs': typeof appauthKanriLogsRoute
|
||||||
'/(app)/(auth)/kanri/settings': typeof appauthKanriSettingsRoute
|
'/(app)/(auth)/kanri/settings': typeof appauthKanriSettingsRoute
|
||||||
'/(app)/(auth)/kanri/users': typeof appauthKanriUsersRoute
|
'/(app)/(auth)/kanri/users': typeof appauthKanriUsersRoute
|
||||||
@@ -167,6 +176,7 @@ export interface FileRouteTypes {
|
|||||||
| '/account/change-password'
|
| '/account/change-password'
|
||||||
| '/account/profile'
|
| '/account/profile'
|
||||||
| '/account/settings'
|
| '/account/settings'
|
||||||
|
| '/kanri/houses'
|
||||||
| '/kanri/logs'
|
| '/kanri/logs'
|
||||||
| '/kanri/settings'
|
| '/kanri/settings'
|
||||||
| '/kanri/users'
|
| '/kanri/users'
|
||||||
@@ -181,6 +191,7 @@ export interface FileRouteTypes {
|
|||||||
| '/account/change-password'
|
| '/account/change-password'
|
||||||
| '/account/profile'
|
| '/account/profile'
|
||||||
| '/account/settings'
|
| '/account/settings'
|
||||||
|
| '/kanri/houses'
|
||||||
| '/kanri/logs'
|
| '/kanri/logs'
|
||||||
| '/kanri/settings'
|
| '/kanri/settings'
|
||||||
| '/kanri/users'
|
| '/kanri/users'
|
||||||
@@ -199,6 +210,7 @@ export interface FileRouteTypes {
|
|||||||
| '/(app)/(auth)/account/change-password'
|
| '/(app)/(auth)/account/change-password'
|
||||||
| '/(app)/(auth)/account/profile'
|
| '/(app)/(auth)/account/profile'
|
||||||
| '/(app)/(auth)/account/settings'
|
| '/(app)/(auth)/account/settings'
|
||||||
|
| '/(app)/(auth)/kanri/houses'
|
||||||
| '/(app)/(auth)/kanri/logs'
|
| '/(app)/(auth)/kanri/logs'
|
||||||
| '/(app)/(auth)/kanri/settings'
|
| '/(app)/(auth)/kanri/settings'
|
||||||
| '/(app)/(auth)/kanri/users'
|
| '/(app)/(auth)/kanri/users'
|
||||||
@@ -305,6 +317,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof appauthKanriLogsRouteImport
|
preLoaderRoute: typeof appauthKanriLogsRouteImport
|
||||||
parentRoute: typeof appauthKanriRouteRoute
|
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': {
|
'/(app)/(auth)/account/settings': {
|
||||||
id: '/(app)/(auth)/account/settings'
|
id: '/(app)/(auth)/account/settings'
|
||||||
path: '/settings'
|
path: '/settings'
|
||||||
@@ -347,6 +366,7 @@ const appauthAccountRouteRouteWithChildren =
|
|||||||
appauthAccountRouteRoute._addFileChildren(appauthAccountRouteRouteChildren)
|
appauthAccountRouteRoute._addFileChildren(appauthAccountRouteRouteChildren)
|
||||||
|
|
||||||
interface appauthKanriRouteRouteChildren {
|
interface appauthKanriRouteRouteChildren {
|
||||||
|
appauthKanriHousesRoute: typeof appauthKanriHousesRoute
|
||||||
appauthKanriLogsRoute: typeof appauthKanriLogsRoute
|
appauthKanriLogsRoute: typeof appauthKanriLogsRoute
|
||||||
appauthKanriSettingsRoute: typeof appauthKanriSettingsRoute
|
appauthKanriSettingsRoute: typeof appauthKanriSettingsRoute
|
||||||
appauthKanriUsersRoute: typeof appauthKanriUsersRoute
|
appauthKanriUsersRoute: typeof appauthKanriUsersRoute
|
||||||
@@ -354,6 +374,7 @@ interface appauthKanriRouteRouteChildren {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const appauthKanriRouteRouteChildren: appauthKanriRouteRouteChildren = {
|
const appauthKanriRouteRouteChildren: appauthKanriRouteRouteChildren = {
|
||||||
|
appauthKanriHousesRoute: appauthKanriHousesRoute,
|
||||||
appauthKanriLogsRoute: appauthKanriLogsRoute,
|
appauthKanriLogsRoute: appauthKanriLogsRoute,
|
||||||
appauthKanriSettingsRoute: appauthKanriSettingsRoute,
|
appauthKanriSettingsRoute: appauthKanriSettingsRoute,
|
||||||
appauthKanriUsersRoute: appauthKanriUsersRoute,
|
appauthKanriUsersRoute: appauthKanriUsersRoute,
|
||||||
|
|||||||
81
src/routes/(app)/(auth)/kanri/houses.tsx
Normal file
81
src/routes/(app)/(auth)/kanri/houses.tsx
Normal file
@@ -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<HTMLInputElement>) => {
|
||||||
|
setSearchKeyword(e.target.value);
|
||||||
|
setPage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="@container/main flex flex-1 flex-col gap-2 p-4">
|
||||||
|
<Skeleton className="h-130 w-full" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-xl flex items-center gap-2">
|
||||||
|
<WarehouseIcon size={24} />
|
||||||
|
{m.houses_page_ui_title()}
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<SearchInput
|
||||||
|
keywords={searchKeyword}
|
||||||
|
setKeyword={setSearchKeyword}
|
||||||
|
onChange={onSearchChange}
|
||||||
|
/>
|
||||||
|
<CreateNewHouse />
|
||||||
|
</div>
|
||||||
|
{data && (
|
||||||
|
<DataTable
|
||||||
|
data={data.result || []}
|
||||||
|
columns={houseColumns}
|
||||||
|
page={page}
|
||||||
|
setPage={setPage}
|
||||||
|
limit={pageLimit}
|
||||||
|
setLimit={setPageLimit}
|
||||||
|
pagination={data.pagination}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { logColumns } from '@/components/audit/audit-columns';
|
import { logColumns } from '@/components/audit/audit-columns';
|
||||||
import DataTable from '@/components/DataTable';
|
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 SearchInput from '@/components/ui/search-input';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import useDebounced from '@/hooks/use-debounced';
|
import useDebounced from '@/hooks/use-debounced';
|
||||||
@@ -37,17 +37,14 @@ function RouteComponent() {
|
|||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="@container/main flex flex-1 flex-col gap-2 p-4">
|
<div className="@container/main flex flex-1 flex-col gap-2 p-4">
|
||||||
<div className="flex flex-col gap-4">
|
<Skeleton className="h-150 w-full" />
|
||||||
<Skeleton className="h-20 w-full" />
|
|
||||||
<Skeleton className="h-130 w-full" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="@container/main flex flex-1 flex-col gap-2 p-4">
|
<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 flex flex-col gap-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">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-xl flex items-center gap-2">
|
<CardTitle className="text-xl flex items-center gap-2">
|
||||||
@@ -55,25 +52,27 @@ function RouteComponent() {
|
|||||||
{m.logs_page_ui_title()}
|
{m.logs_page_ui_title()}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<SearchInput
|
||||||
|
keywords={searchKeyword}
|
||||||
|
setKeyword={setSearchKeyword}
|
||||||
|
onChange={onSearchChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{data?.result && (
|
||||||
|
<DataTable
|
||||||
|
data={data.result || []}
|
||||||
|
columns={logColumns}
|
||||||
|
page={page}
|
||||||
|
setPage={setPage}
|
||||||
|
limit={pageLimit}
|
||||||
|
setLimit={setPageLimit}
|
||||||
|
pagination={data.pagination}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="flex items-center">
|
|
||||||
<SearchInput
|
|
||||||
keywords={searchKeyword}
|
|
||||||
setKeyword={setSearchKeyword}
|
|
||||||
onChange={onSearchChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{data?.result && (
|
|
||||||
<DataTable
|
|
||||||
data={data.result || []}
|
|
||||||
columns={logColumns}
|
|
||||||
page={page}
|
|
||||||
setPage={setPage}
|
|
||||||
limit={pageLimit}
|
|
||||||
setLimit={setPageLimit}
|
|
||||||
pagination={data.pagination}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import DataTable from '@/components/DataTable';
|
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 SearchInput from '@/components/ui/search-input';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import AddNewUserButton from '@/components/user/add-new-user-dialog';
|
import AddNewUserButton from '@/components/user/add-new-user-dialog';
|
||||||
@@ -39,44 +39,43 @@ function RouteComponent() {
|
|||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="@container/main flex flex-1 flex-col gap-2 p-4">
|
<div className="@container/main flex flex-1 flex-col gap-2 p-4">
|
||||||
<div className="flex flex-col gap-4">
|
<Skeleton className="h-130 w-full" />
|
||||||
<Skeleton className="h-20 w-full" />
|
|
||||||
<Skeleton className="h-130 w-full" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="@container/main flex flex-1 flex-col gap-2 p-4">
|
<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 flex flex-col gap-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">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader className="border-b">
|
||||||
<CardTitle className="text-xl flex items-center gap-2">
|
<CardTitle className="text-xl flex items-center gap-2">
|
||||||
<UsersIcon size={24} />
|
<UsersIcon size={24} />
|
||||||
{m.users_page_ui_title()}
|
{m.users_page_ui_title()}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<SearchInput
|
||||||
|
keywords={searchKeyword}
|
||||||
|
setKeyword={setSearchKeyword}
|
||||||
|
onChange={onSearchChange}
|
||||||
|
/>
|
||||||
|
<AddNewUserButton />
|
||||||
|
</div>
|
||||||
|
{data && (
|
||||||
|
<DataTable
|
||||||
|
data={data.result || []}
|
||||||
|
columns={userColumns}
|
||||||
|
page={page}
|
||||||
|
setPage={setPage}
|
||||||
|
limit={pageLimit}
|
||||||
|
setLimit={setPageLimit}
|
||||||
|
pagination={data.pagination}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<SearchInput
|
|
||||||
keywords={searchKeyword}
|
|
||||||
setKeyword={setSearchKeyword}
|
|
||||||
onChange={onSearchChange}
|
|
||||||
/>
|
|
||||||
<AddNewUserButton />
|
|
||||||
</div>
|
|
||||||
{data && (
|
|
||||||
<DataTable
|
|
||||||
data={data.result || []}
|
|
||||||
columns={userColumns}
|
|
||||||
page={page}
|
|
||||||
setPage={setPage}
|
|
||||||
limit={pageLimit}
|
|
||||||
setLimit={setPageLimit}
|
|
||||||
pagination={data.pagination}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { prisma } from '@/db';
|
import { prisma } from '@/db';
|
||||||
import { AuditWhereInput } from '@/generated/prisma/models';
|
import { AuditWhereInput } from '@/generated/prisma/models';
|
||||||
import { authMiddleware } from '@/lib/middleware';
|
import { authMiddleware } from '@/lib/middleware';
|
||||||
|
import { parseError } from '@/utils/helper';
|
||||||
import { createServerFn } from '@tanstack/react-start';
|
import { createServerFn } from '@tanstack/react-start';
|
||||||
import { auditListSchema } from './audit.schema';
|
import { auditListSchema } from './audit.schema';
|
||||||
|
|
||||||
@@ -62,7 +63,8 @@ export const getAllAudit = createServerFn({ method: 'GET' })
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.error(error);
|
||||||
throw error;
|
const { message, code } = parseError(error);
|
||||||
|
throw { message, code };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
67
src/service/house.api.ts
Normal file
67
src/service/house.api.ts
Normal file
@@ -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 };
|
||||||
|
}
|
||||||
|
});
|
||||||
12
src/service/house.schema.ts
Normal file
12
src/service/house.schema.ts
Normal file
@@ -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(),
|
||||||
|
});
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { getSession } from '@/lib/auth/session';
|
import { getSession } from '@/lib/auth/session';
|
||||||
import { queryOptions } from '@tanstack/react-query';
|
import { queryOptions } from '@tanstack/react-query';
|
||||||
import { getAllAudit } from './audit.api';
|
import { getAllAudit } from './audit.api';
|
||||||
|
import { getAllHouse } from './house.api';
|
||||||
import {
|
import {
|
||||||
getAdminSettings,
|
getAdminSettings,
|
||||||
getCurrentUserLanguage,
|
getCurrentUserLanguage,
|
||||||
@@ -55,3 +56,12 @@ export const usersQueries = {
|
|||||||
queryFn: () => getAllUser({ data: params }),
|
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 }),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { prisma } from '@/db';
|
import { prisma } from '@/db';
|
||||||
import { Audit, Setting } from '@/generated/prisma/client';
|
import { Audit, Setting } from '@/generated/prisma/client';
|
||||||
|
import { parseError } from '@/utils/helper';
|
||||||
|
|
||||||
type AdminSettingValue = Pick<Setting, 'id' | 'key' | 'value'>;
|
type AdminSettingValue = Pick<Setting, 'id' | 'key' | 'value'>;
|
||||||
|
|
||||||
@@ -48,7 +49,8 @@ export const createAuditLog = async (data: Omit<Audit, 'id' | 'createdAt'>) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.error(error);
|
||||||
throw error;
|
const { message, code } = parseError(error);
|
||||||
|
throw { message, code };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { prisma } from '@/db';
|
import { prisma } from '@/db';
|
||||||
import { authMiddleware } from '@/lib/middleware';
|
import { authMiddleware } from '@/lib/middleware';
|
||||||
import { extractDiffObjects } from '@/utils/helper';
|
import { extractDiffObjects, parseError } from '@/utils/helper';
|
||||||
import { createServerFn } from '@tanstack/react-start';
|
import { createServerFn } from '@tanstack/react-start';
|
||||||
import { createAuditLog, getAllAdminSettings } from './repository';
|
import { createAuditLog, getAllAdminSettings } from './repository';
|
||||||
import { settingSchema, userSettingSchema } from './setting.schema';
|
import { settingSchema, userSettingSchema } from './setting.schema';
|
||||||
@@ -25,8 +25,9 @@ export const getCurrentUserLanguage = createServerFn({ method: 'GET' })
|
|||||||
|
|
||||||
return value.language;
|
return value.language;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.error(error);
|
||||||
throw error;
|
const { message, code } = parseError(error);
|
||||||
|
throw { message, code };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,8 +72,9 @@ export const updateAdminSettings = createServerFn({ method: 'POST' })
|
|||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.error(error);
|
||||||
throw 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,
|
value: JSON.parse(settings.value) as UserSetting,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.error(error);
|
||||||
throw error;
|
const { message, code } = parseError(error);
|
||||||
|
throw { message, code };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -147,7 +150,8 @@ export const updateUserSettings = createServerFn({ method: 'POST' })
|
|||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.error(error);
|
||||||
throw error;
|
const { message, code } = parseError(error);
|
||||||
|
throw { message, code };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,33 +20,39 @@ export const getAllUser = createServerFn({ method: 'GET' })
|
|||||||
.middleware([authMiddleware])
|
.middleware([authMiddleware])
|
||||||
.inputValidator(userListSchema)
|
.inputValidator(userListSchema)
|
||||||
.handler(async ({ data }) => {
|
.handler(async ({ data }) => {
|
||||||
const headers = getRequestHeaders();
|
try {
|
||||||
const { page, limit, keyword } = data;
|
const headers = getRequestHeaders();
|
||||||
|
const { page, limit, keyword } = data;
|
||||||
|
|
||||||
const list = await auth.api.listUsers({
|
const list = await auth.api.listUsers({
|
||||||
query: {
|
query: {
|
||||||
searchValue: keyword,
|
searchValue: keyword,
|
||||||
searchField: 'name',
|
searchField: 'name',
|
||||||
searchOperator: 'contains',
|
searchOperator: 'contains',
|
||||||
sortBy: 'createdAt',
|
sortBy: 'createdAt',
|
||||||
sortDirection: 'asc',
|
sortDirection: 'asc',
|
||||||
limit,
|
limit,
|
||||||
offset: (page - 1) * limit,
|
offset: (page - 1) * limit,
|
||||||
},
|
},
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
const totalItem = list.total;
|
const totalItem = list.total;
|
||||||
const totalPage = Math.ceil(totalItem / limit);
|
const totalPage = Math.ceil(totalItem / limit);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: list.users,
|
result: list.users,
|
||||||
pagination: {
|
pagination: {
|
||||||
currentPage: page,
|
currentPage: page,
|
||||||
totalPage,
|
totalPage,
|
||||||
totalItem,
|
totalItem,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
const { message, code } = parseError(error);
|
||||||
|
throw { message, code };
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setUserPassword = createServerFn({ method: 'POST' })
|
export const setUserPassword = createServerFn({ method: 'POST' })
|
||||||
@@ -74,6 +80,7 @@ export const setUserPassword = createServerFn({ method: 'POST' })
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
const { message, code } = parseError(error);
|
const { message, code } = parseError(error);
|
||||||
throw { message, code };
|
throw { message, code };
|
||||||
}
|
}
|
||||||
@@ -112,6 +119,7 @@ export const updateUserInformation = createServerFn({ method: 'POST' })
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
const { message, code } = parseError(error);
|
const { message, code } = parseError(error);
|
||||||
throw { message, code };
|
throw { message, code };
|
||||||
}
|
}
|
||||||
@@ -150,6 +158,7 @@ export const setUserRole = createServerFn({ method: 'POST' })
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
const { message, code } = parseError(error);
|
const { message, code } = parseError(error);
|
||||||
throw { message, code };
|
throw { message, code };
|
||||||
}
|
}
|
||||||
@@ -185,6 +194,7 @@ export const banUser = createServerFn({ method: 'POST' })
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
const { message, code } = parseError(error);
|
const { message, code } = parseError(error);
|
||||||
throw { message, code };
|
throw { message, code };
|
||||||
}
|
}
|
||||||
@@ -215,6 +225,7 @@ export const unbanUser = createServerFn({ method: 'POST' })
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
const { message, code } = parseError(error);
|
const { message, code } = parseError(error);
|
||||||
throw { message, code };
|
throw { message, code };
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/types/db.d.ts
vendored
17
src/types/db.d.ts
vendored
@@ -11,4 +11,21 @@ declare global {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
type OrganizationWithMembers = Prisma.OrganizationGetPayload<{
|
||||||
|
include: {
|
||||||
|
members: {
|
||||||
|
select: {
|
||||||
|
role: true;
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
name: true;
|
||||||
|
email: true;
|
||||||
|
image: true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user