Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const Page = ({ params }: { params: { slug: string; id: string } }) => {
}

fetchDetails()
}, [params.id])
}, [params.id, t, toast])

return (
<div className="p-6 bg-white dark:bg-gray-800 rounded-md shadow-md text-black dark:text-white">
Expand Down
97 changes: 97 additions & 0 deletions src/app/(private)/role-listing/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
'use client'

import { useEffect, useMemo, useState } from 'react'
import PrivateRoute from '@/app/hocs/isAuth'
import { useTranslation } from 'react-i18next'
import { useToast } from '@/components/ui/use-toast'
import { useSearchParams } from 'next/navigation'
import { Permission } from '@/constants/permissions'
import { postRoleListing } from '@/modules/roleListing/service'
import { searchParamsSchema } from '@/modules/roleListing/constants/searchParamsSchema'
import { RoleListing } from '@/modules/roleListing/constants/types'
import { useDataTable } from '@/app/hocs/useDataTable'
import { Toaster } from '@/components/ui/toaster'
import { DataTable, DataTableToolbar } from '@/components/dataTable'
import { columns } from '@/modules/roleListing/components/columns'
import filterFields from '@/modules/roleListing/constants/filterFields'

const Page = () => {
const searchParams = useSearchParams()
const search = searchParamsSchema.parse(
Object.fromEntries(searchParams.entries()),
)

const { t } = useTranslation()
const { toast } = useToast()
const [data, setData] = useState<RoleListing>({
content: [],
totalPageCount: 0,
})
const [isLoading, setIsLoading] = useState<boolean>(false)
const [error, setError] = useState<string | null>(null)
const searchParamsString = useMemo(() => JSON.stringify(search), [search])

useEffect(() => {
setIsLoading(true)
postRoleListing({
page: search.page,
per_page: search.per_page,
sort: search.sort,
status: search.status,
name: search.name,
createdAt: search.createdAt,
updatedAt: search.updatedAt,
})
.then((responseData) => {
setData(responseData.data.response)
})
.catch((error) => {
setError(error.message)
toast({
title: t('error'),
description: t('defaultError'),
variant: 'destructive',
})
})
.finally(() => setIsLoading(false))
}, [
searchParamsString,
search.createdAt,
search.name,
search.page,
search.per_page,
search.sort,
search.status,
search.updatedAt,
t,
toast,
])

const { table } = useDataTable({
data: data.content,
columns,
pageCount: data.totalPageCount,
filterFields,
})

return (
<PrivateRoute requiredPermissions={[Permission.ROLE_LIST]}>
<div className="space-y-1">
{error && <Toaster />}
<DataTable
className="px-2"
table={table}
loading={isLoading}
enableRowClick
>
<div className="flex flex-col w-full gap-4">
<h1 className="text-2xl font-medium">{t('roleListing')}</h1>
<DataTableToolbar table={table} filterFields={filterFields} />
</div>
</DataTable>
</div>
</PrivateRoute>
)
}

export default Page
2 changes: 1 addition & 1 deletion src/app/(public)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const Page = () => {
if (tokenInfo) {
router.push('/dashboard')
}
}, [tokenInfo])
}, [tokenInfo, router])

return tokenInfo ? (
<></>
Expand Down
1 change: 1 addition & 0 deletions src/components/dataTable/dataTableSearchField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const DataTableSearchField = <TData,>({
table,
}: DataTableSearchFieldProps<TData>) => {
const schema = getValidationSchema(field.value)

const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const inputValue = event.target.value
const validation = schema.safeParse(inputValue)
Expand Down
5 changes: 2 additions & 3 deletions src/components/dataTable/dataTableSort.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import {
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip'
import i18next from 'i18next'
import i18n from 'i18next'
import { BiSort, BiSortDown, BiSortUp } from 'react-icons/bi'

const DataTableSort = ({ column }: { column: any }) => {
const DataTableSort = ({ column, label }: { column: any, label: string }) => {
return (
<TooltipProvider>
<Tooltip>
Expand All @@ -18,7 +17,7 @@ const DataTableSort = ({ column }: { column: any }) => {
onClick={() => column.toggleSorting()}
>
<span className="flex items-center gap-2">
{i18next.t('createdAt')}
{label}
<SortIcon sort={column.getIsSorted()} />
</span>
</TooltipTrigger>
Expand Down
43 changes: 33 additions & 10 deletions src/components/ui/filterInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,56 @@ import { z } from 'zod'
import { toast } from '@/components/ui/use-toast'
import { cn } from '@/lib/utils'

const FilterInput = ({ param, min = 2, max = 100 }: { param: string, min?: number, max?: number }) => {
const FilterInput = ({
param,
min = 2,
max = 100,
}: {
param: string
min?: number
max?: number
}) => {
const searchParams = useSearchParams()
const router = useRouter()
const pathname = usePathname()
const initialValue = searchParams.get(param)
const [search, setSearch] = useState(initialValue || '')
const debouncedSearch = useDebounce(search, 500)

const schema = z.string().min(min, { message: i18next.t('minLength', { field: min }) }).max(max, { message: i18next.t('maxLength', { field: max }) })
const schema = z
.string()
.min(min, { message: i18next.t('minLength', { field: min }) })
.max(max, { message: i18next.t('maxLength', { field: max }) })

useEffect(() => {
const newSearchParams = new URLSearchParams()
if (debouncedSearch) {
const validation = schema.safeParse(debouncedSearch)
if (validation.success) {
newSearchParams.set(param, debouncedSearch)
router.replace(`${pathname}?${newSearchParams.toString()}`, { scroll: false })
router.replace(`${pathname}?${newSearchParams.toString()}`, {
scroll: false,
})
} else {
toast({ title: validation.error.errors[0].message, variant: 'destructive' })
toast({
title: validation.error.errors[0].message,
variant: 'destructive',
})
}
} else if (!debouncedSearch && initialValue) {
newSearchParams.delete(param)
router.replace(`${pathname}?${newSearchParams.toString()}`, { scroll: false })
router.replace(`${pathname}?${newSearchParams.toString()}`, {
scroll: false,
})
}
}, [debouncedSearch])
}, [debouncedSearch, initialValue, param, pathname, router, schema])

const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.currentTarget.value)
}, [])
const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.currentTarget.value)
},
[],
)

return (
<div className="relative">
Expand All @@ -45,7 +66,9 @@ const FilterInput = ({ param, min = 2, max = 100 }: { param: string, min?: numbe
placeholder=""
value={search}
onChange={handleInputChange}
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')}
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',
)}
/>
<Label
htmlFor={param}
Expand Down
8 changes: 7 additions & 1 deletion src/constants/menuItems.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BusFront, HomeIcon, UserRoundCheck } from 'lucide-react'
import { BusFront, HomeIcon, UserRoundCheck, ClipboardPenLine } from 'lucide-react'
import React from 'react'
import { Permission } from '@/constants/permissions'

Expand Down Expand Up @@ -27,4 +27,10 @@ export const MenuItems: Menu[] = [
icon: BusFront,
requiredPermissions: [Permission.EVACUATION_LIST],
},
{
key: '/role-listing',
label: 'roleListing',
icon: ClipboardPenLine,
requiredPermissions: [Permission.ROLE_LIST],
},
]
7 changes: 6 additions & 1 deletion src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"page": "Page",
"adminRegistrationApplications": "Admin Registration Applications",
"emergencyEvacuationApplications": "Emergency Evacuation Applications",
"roleListing": "Role Listing",
"all": "All",
"status": "Status",
"waiting": "Waiting",
Expand All @@ -20,6 +21,7 @@
"active": "Active",
"passive": "Passive",
"pending": "Pending",
"deleted": "Deleted",
"inReview": "In Review",
"inProgress": "In Progress",
"receivedFirstApprove": "Received First Approve",
Expand Down Expand Up @@ -81,5 +83,8 @@
"city": "City of Locate",
"emailAddress": "E-mail Address",
"seatingCountValidationMessage": "Only positive numbers with a maximum of {{field}} digits are allowed.",
"referenceNumberValidationMessage": "Only positive numerical values are allowed."
"referenceNumberValidationMessage": "Only positive numerical values are allowed.",
"name": "Role Name",
"createdDateTime": "Creation Date and Time",
"updatedDateTime": "Update Date and Time"
}
7 changes: 6 additions & 1 deletion src/i18n/locales/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"page": "Sayfa",
"adminRegistrationApplications": "Yönetici Kayıt Başvuruları",
"emergencyEvacuationApplications": "Acil Durum Tahliye Başvurusu",
"roleListing": "Rol Listeleme",
"all": "Hepsini Seç",
"status": "Durum",
"waiting": "Beklemede",
Expand All @@ -19,6 +20,7 @@
"notVerified": "Doğrulanmadı",
"active": "Aktif",
"passive": "Pasif",
"deleted": "Silinmiş",
"pending": "Bekleniyor",
"inReview": "İnceleniyor",
"inProgress": "İşlemde",
Expand Down Expand Up @@ -81,5 +83,8 @@
"targetDistrict": "Tahliye Sağlanılacak İlçe",
"phoneNumber": "Telefon Numarası",
"seatingCountValidationMessage": "Sadece maksimum {{field}} haneli, pozitif sayı değerleri alabilir.",
"referenceNumberValidationMessage": "Sadece pozitif sayısal değerler alabilir."
"referenceNumberValidationMessage": "Sadece pozitif sayısal değerler alabilir.",
"name": "Rol Adı",
"createdDateTime": "Oluşturulma Tarihi ve Saati",
"updatedDateTime": "Güncellenme Tarihi ve Saat"
}
5 changes: 4 additions & 1 deletion src/lib/formatDateTime.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { DateTime } from "luxon";

export const formatDateTime = (date: string) => {
export const formatDateTime = (date: string | null) => {
if (!date) {
return "";
}
return DateTime
.fromISO(date, { zone: "UTC" })
.setZone(DateTime.local().zoneName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const columns: ColumnDef<AdminRegistrationApplication>[] = [
{
accessorKey: 'createdAt',
header: ({ column }) => {
return <DataTableSort column={column} />
return <DataTableSort column={column} label={i18next.t('createdAt')}/>
},
size: 155,
cell: ({ row }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const columns: ColumnDef<EmergencyEvacuationApplicationsTableProps>[] = [
{
accessorKey: 'createdAt',
header: ({ column }) => {
return <DataTableSort column={column} />
return <DataTableSort column={column} label={i18next.t('createdAt')} />
},
cell: ({ row }) => {
return <div className="px-2">{formatDateTime(row.getValue('createdAt'))}</div>
Expand Down
37 changes: 37 additions & 0 deletions src/modules/roleListing/components/columns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ColumnDef } from '@tanstack/table-core'
import i18next from 'i18next'
import { formatDateTime } from '@/lib/formatDateTime'
import DataTableSort from '@/components/dataTable/dataTableSort'
import { RoleListingTableProps } from '../constants/types';
import Status from './status';

export const columns: ColumnDef<RoleListingTableProps>[] = [
{
accessorKey: 'name',
header: () => i18next.t("name"),
cell: ({ row }) => row.original.name,
size: 200,
},
{
accessorKey: 'status',
header: () => i18next.t('status'),
cell: ({ row }) => <Status status={row.getValue('status')} />,
size: 100,
},
{
accessorKey: 'createdAt',
header: ({ column }) => {
return <DataTableSort column={column} label={i18next.t('createdAt')} />
},
cell: ({ row }) => {
return <div className="px-2">{formatDateTime(row.getValue('createdAt'))}</div>
},
size: 170,
},
{
accessorKey: 'updatedAt',
header: () => i18next.t('updatedDateTime'),
cell: ({ row }) => <div className="px-2">{formatDateTime(row.getValue('updatedAt'))}</div>,
size: 170,
},
];
37 changes: 37 additions & 0 deletions src/modules/roleListing/components/status.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { cn } from '@/lib/utils'
import { StatusProps } from '../constants/types'
import { StatusData } from '../constants/status'

const Status = ({ status }: StatusProps) => {
const [statusData, setStatusData] = useState<{
label: string
color: string
}>({ label: '', color: '' })
const { t } = useTranslation()

const getStatus = (status: string) => {
const statusItem = StatusData.find((item) => item.value === status)
const color = statusItem ? statusItem.color : ''
const label = statusItem ? statusItem.label : ''
return { color, label }
}

useEffect(() => {
setStatusData(getStatus(status))
}, [status])

return (
<span
className={cn(
'inline-flex items-center rounded-md px-2 py-1 text-xs',
statusData?.color,
)}
>
{t(`${statusData.label}`)}
</span>
)
}

export default Status
Loading
Loading