Skip to content

Commit d9276c8

Browse files
committed
AYS-298 | Role Listing Page (#204)
1 parent 371abaf commit d9276c8

File tree

19 files changed

+319
-19
lines changed

19 files changed

+319
-19
lines changed

src/app/(private)/admin-registration-applications/[id]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const Page = ({ params }: { params: { slug: string; id: string } }) => {
6666
}
6767

6868
fetchDetails()
69-
}, [params.id])
69+
}, [params.id, t, toast])
7070

7171
return (
7272
<PrivateRoute requiredPermissions={[Permission.APPLICATION_DETAIL]}>
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
'use client'
2+
3+
import { useEffect, useMemo, useState } from 'react'
4+
import PrivateRoute from '@/app/hocs/isAuth'
5+
import { useTranslation } from 'react-i18next'
6+
import { useToast } from '@/components/ui/use-toast'
7+
import { useSearchParams } from 'next/navigation'
8+
import { Permission } from '@/constants/permissions'
9+
import { postRoleListing } from '@/modules/roleListing/service'
10+
import { searchParamsSchema } from '@/modules/roleListing/constants/searchParamsSchema'
11+
import { RoleListing } from '@/modules/roleListing/constants/types'
12+
import { useDataTable } from '@/app/hocs/useDataTable'
13+
import { DataTable, DataTableToolbar } from '@/components/dataTable'
14+
import { columns } from '@/modules/roleListing/components/columns'
15+
import filterFields from '@/modules/roleListing/constants/filterFields'
16+
17+
const Page = () => {
18+
const searchParams = useSearchParams()
19+
const search = searchParamsSchema.parse(
20+
Object.fromEntries(searchParams.entries()),
21+
)
22+
23+
const { t } = useTranslation()
24+
const { toast } = useToast()
25+
const [data, setData] = useState<RoleListing>({
26+
content: [],
27+
totalPageCount: 0,
28+
})
29+
const [isLoading, setIsLoading] = useState<boolean>(false)
30+
const searchParamsString = useMemo(() => JSON.stringify(search), [search])
31+
32+
useEffect(() => {
33+
setIsLoading(true)
34+
postRoleListing({
35+
page: search.page,
36+
per_page: search.per_page,
37+
sort: search.sort,
38+
status: search.status,
39+
name: search.name,
40+
createdAt: search.createdAt,
41+
updatedAt: search.updatedAt,
42+
})
43+
.then((responseData) => {
44+
setData(responseData.data.response)
45+
})
46+
.catch(() => {
47+
toast({
48+
title: t('error'),
49+
description: t('defaultError'),
50+
variant: 'destructive',
51+
})
52+
})
53+
.finally(() => setIsLoading(false))
54+
}, [
55+
searchParamsString,
56+
search.createdAt,
57+
search.name,
58+
search.page,
59+
search.per_page,
60+
search.sort,
61+
search.status,
62+
search.updatedAt,
63+
t,
64+
toast,
65+
])
66+
67+
const { table } = useDataTable({
68+
data: data.content,
69+
columns,
70+
pageCount: data.totalPageCount,
71+
filterFields,
72+
})
73+
74+
return (
75+
<PrivateRoute requiredPermissions={[Permission.ROLE_LIST]}>
76+
<div className="space-y-1">
77+
<DataTable
78+
className="px-2"
79+
table={table}
80+
loading={isLoading}
81+
enableRowClick
82+
>
83+
<div className="flex flex-col w-full gap-4">
84+
<h1 className="text-2xl font-medium">{t('roleListing')}</h1>
85+
<DataTableToolbar table={table} filterFields={filterFields} />
86+
</div>
87+
</DataTable>
88+
</div>
89+
</PrivateRoute>
90+
)
91+
}
92+
93+
export default Page

src/app/(public)/login/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ const Page = () => {
9393
if (tokenInfo) {
9494
router.push('/dashboard')
9595
}
96-
}, [tokenInfo])
96+
}, [tokenInfo, router])
9797

9898
return tokenInfo ? (
9999
<></>

src/components/dataTable/dataTableSearchField.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface DataTableSearchFieldProps<TData>
1818

1919
const DataTableSearchField = <TData, >({ field, table }: DataTableSearchFieldProps<TData>) => {
2020
const schema = getValidationSchema(field.value)
21+
2122
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
2223
const inputValue = event.target.value
2324
const validation = schema.safeParse(inputValue)

src/components/dataTable/dataTableSort.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ import {
55
TooltipProvider,
66
TooltipTrigger,
77
} from '@/components/ui/tooltip'
8-
import i18next from 'i18next'
98
import i18n from 'i18next'
109
import { BiSort, BiSortDown, BiSortUp } from 'react-icons/bi'
1110

12-
const DataTableSort = ({ column }: { column: any }) => {
11+
const DataTableSort = ({ column, label }: { column: any, label: string }) => {
1312
return (
1413
<TooltipProvider>
1514
<Tooltip>
@@ -18,7 +17,7 @@ const DataTableSort = ({ column }: { column: any }) => {
1817
onClick={() => column.toggleSorting()}
1918
>
2019
<span className="flex items-center gap-2">
21-
{i18next.t('createdAt')}
20+
{label}
2221
<SortIcon sort={column.getIsSorted()} />
2322
</span>
2423
</TooltipTrigger>

src/components/ui/filterInput.tsx

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,56 @@ import { z } from 'zod'
88
import { toast } from '@/components/ui/use-toast'
99
import { cn } from '@/lib/utils'
1010

11-
const FilterInput = ({ param, min = 2, max = 100 }: { param: string, min?: number, max?: number }) => {
11+
const FilterInput = ({
12+
param,
13+
min = 2,
14+
max = 100,
15+
}: {
16+
param: string
17+
min?: number
18+
max?: number
19+
}) => {
1220
const searchParams = useSearchParams()
1321
const router = useRouter()
1422
const pathname = usePathname()
1523
const initialValue = searchParams.get(param)
1624
const [search, setSearch] = useState(initialValue || '')
1725
const debouncedSearch = useDebounce(search, 500)
1826

19-
const schema = z.string().min(min, { message: i18next.t('minLength', { field: min }) }).max(max, { message: i18next.t('maxLength', { field: max }) })
27+
const schema = z
28+
.string()
29+
.min(min, { message: i18next.t('minLength', { field: min }) })
30+
.max(max, { message: i18next.t('maxLength', { field: max }) })
2031

2132
useEffect(() => {
2233
const newSearchParams = new URLSearchParams()
2334
if (debouncedSearch) {
2435
const validation = schema.safeParse(debouncedSearch)
2536
if (validation.success) {
2637
newSearchParams.set(param, debouncedSearch)
27-
router.replace(`${pathname}?${newSearchParams.toString()}`, { scroll: false })
38+
router.replace(`${pathname}?${newSearchParams.toString()}`, {
39+
scroll: false,
40+
})
2841
} else {
29-
toast({ title: validation.error.errors[0].message, variant: 'destructive' })
42+
toast({
43+
title: validation.error.errors[0].message,
44+
variant: 'destructive',
45+
})
3046
}
3147
} else if (!debouncedSearch && initialValue) {
3248
newSearchParams.delete(param)
33-
router.replace(`${pathname}?${newSearchParams.toString()}`, { scroll: false })
49+
router.replace(`${pathname}?${newSearchParams.toString()}`, {
50+
scroll: false,
51+
})
3452
}
35-
}, [debouncedSearch])
53+
}, [debouncedSearch, initialValue, param, pathname, router, schema])
3654

37-
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
38-
setSearch(e.currentTarget.value)
39-
}, [])
55+
const handleInputChange = useCallback(
56+
(e: React.ChangeEvent<HTMLInputElement>) => {
57+
setSearch(e.currentTarget.value)
58+
},
59+
[],
60+
)
4061

4162
return (
4263
<div className="relative">
@@ -45,7 +66,9 @@ const FilterInput = ({ param, min = 2, max = 100 }: { param: string, min?: numbe
4566
placeholder=""
4667
value={search}
4768
onChange={handleInputChange}
48-
className={cn('block focus-visible:ring-0 focus-visible:ring-offset-0 p-3 w-full text-sm text-gray-900 bg-transparent rounded-lg border-[2px] border-gray-200 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer')}
69+
className={cn(
70+
'block focus-visible:ring-0 focus-visible:ring-offset-0 p-3 w-full text-sm text-gray-900 bg-transparent rounded-lg border-[2px] border-gray-200 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer',
71+
)}
4972
/>
5073
<Label
5174
htmlFor={param}

src/constants/menuItems.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BusFront, HomeIcon, UserRoundCheck } from 'lucide-react'
1+
import { BusFront, HomeIcon, UserRoundCheck, ClipboardPenLine } from 'lucide-react'
22
import React from 'react'
33
import { Permission } from '@/constants/permissions'
44

@@ -27,4 +27,10 @@ export const MenuItems: Menu[] = [
2727
icon: BusFront,
2828
requiredPermissions: [Permission.EVACUATION_LIST],
2929
},
30+
{
31+
key: '/role-listing',
32+
label: 'roleListing',
33+
icon: ClipboardPenLine,
34+
requiredPermissions: [Permission.ROLE_LIST],
35+
},
3036
]

src/i18n/locales/en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"users": "Users",
99
"home": "Home",
1010
"page": "Page",
11+
"roleListing": "Role Listing",
1112
"all": "All",
1213
"status": "Status",
1314
"waiting": "Waiting",
@@ -18,6 +19,7 @@
1819
"active": "Active",
1920
"passive": "Passive",
2021
"pending": "Pending",
22+
"deleted": "Deleted",
2123
"inReview": "In Review",
2224
"inProgress": "In Progress",
2325
"receivedFirstApprove": "Received First Approve",
@@ -75,6 +77,9 @@
7577
"emailAddress": "E-mail Address",
7678
"seatingCountValidationMessage": "Only positive numbers with a maximum of {{field}} digits are allowed.",
7779
"referenceNumberValidationMessage": "Only positive numerical values are allowed.",
80+
"name": "Role Name",
81+
"createdDateTime": "Creation Date and Time",
82+
"updatedDateTime": "Update Date and Time",
7883
"adminRegistrationApplications": {
7984
"title": "Admin Registration Applications",
8085
"detailsTitle": "Admin Registration Applications Details",

src/i18n/locales/tr.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"users": "Kullanıcılar",
99
"home": "Ana Sayfa",
1010
"page": "Sayfa",
11+
"roleListing": "Rol Listeleme",
1112
"all": "Hepsini Seç",
1213
"status": "Durum",
1314
"waiting": "Beklemede",
@@ -17,6 +18,7 @@
1718
"notVerified": "Doğrulanmadı",
1819
"active": "Aktif",
1920
"passive": "Pasif",
21+
"deleted": "Silinmiş",
2022
"pending": "Bekleniyor",
2123
"inReview": "İnceleniyor",
2224
"inProgress": "İşlemde",
@@ -74,6 +76,9 @@
7476
"phoneNumber": "Telefon Numarası",
7577
"seatingCountValidationMessage": "Sadece maksimum {{field}} haneli, pozitif sayı değerleri alabilir.",
7678
"referenceNumberValidationMessage": "Sadece pozitif sayısal değerler alabilir.",
79+
"name": "Rol Adı",
80+
"createdDateTime": "Oluşturulma Tarihi ve Saati",
81+
"updatedDateTime": "Güncellenme Tarihi ve Saat",
7782
"adminRegistrationApplications": {
7883
"title": "Yönetici Kayıt Başvuruları",
7984
"detailsTitle": "Yönetici Kayıt Başvuru Detayı",

src/lib/formatDateTime.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { DateTime } from "luxon";
22

3-
export const formatDateTime = (date: string) => {
3+
export const formatDateTime = (date: string | null) => {
4+
if (!date) {
5+
return "";
6+
}
47
return DateTime
58
.fromISO(date, { zone: "UTC" })
69
.setZone(DateTime.local().zoneName)

0 commit comments

Comments
 (0)