Sfoglia il codice sorgente

refactor: optimize states in users/tasks context (#71)

* refactor: optimize states in tasks context

* refactor: optimize states in users context
Sat Naing 1 anno fa
parent
commit
397f46bc30

+ 2 - 2
src/features/tasks/components/data-table-row-actions.tsx

@@ -15,7 +15,7 @@ import {
   DropdownMenuSubTrigger,
   DropdownMenuTrigger,
 } from '@/components/ui/dropdown-menu'
-import { useTasksContext } from '../context/tasks-context'
+import { useTasks } from '../context/tasks-context'
 import { labels } from '../data/data'
 import { taskSchema } from '../data/schema'
 
@@ -28,7 +28,7 @@ export function DataTableRowActions<TData>({
 }: DataTableRowActionsProps<TData>) {
   const task = taskSchema.parse(row.original)
 
-  const { setOpen, setCurrentRow } = useTasksContext()
+  const { setOpen, setCurrentRow } = useTasks()
 
   return (
     <DropdownMenu modal={false}>

+ 78 - 0
src/features/tasks/components/tasks-dialogs.tsx

@@ -0,0 +1,78 @@
+import { toast } from '@/hooks/use-toast'
+import { ConfirmDialog } from '@/components/confirm-dialog'
+import { useTasks } from '../context/tasks-context'
+import { TasksImportDialog } from './tasks-import-dialog'
+import { TasksMutateDrawer } from './tasks-mutate-drawer'
+
+export function TasksDialogs() {
+  const { open, setOpen, currentRow, setCurrentRow } = useTasks()
+  return (
+    <>
+      <TasksMutateDrawer
+        key='task-create'
+        open={open === 'create'}
+        onOpenChange={() => setOpen('create')}
+      />
+
+      <TasksImportDialog
+        key='tasks-import'
+        open={open === 'import'}
+        onOpenChange={() => setOpen('import')}
+      />
+
+      {currentRow && (
+        <>
+          <TasksMutateDrawer
+            key={`task-update-${currentRow.id}`}
+            open={open === 'update'}
+            onOpenChange={() => {
+              setOpen('update')
+              setTimeout(() => {
+                setCurrentRow(null)
+              }, 500)
+            }}
+            currentRow={currentRow}
+          />
+
+          <ConfirmDialog
+            key='task-delete'
+            destructive
+            open={open === 'delete'}
+            onOpenChange={() => {
+              setOpen('delete')
+              setTimeout(() => {
+                setCurrentRow(null)
+              }, 500)
+            }}
+            handleConfirm={() => {
+              setOpen(null)
+              setTimeout(() => {
+                setCurrentRow(null)
+              }, 500)
+              toast({
+                title: 'The following task has been deleted:',
+                description: (
+                  <pre className='mt-2 w-[340px] rounded-md bg-slate-950 p-4'>
+                    <code className='text-white'>
+                      {JSON.stringify(currentRow, null, 2)}
+                    </code>
+                  </pre>
+                ),
+              })
+            }}
+            className='max-w-md'
+            title={`Delete this task: ${currentRow.id} ?`}
+            desc={
+              <>
+                You are about to delete a task with the ID{' '}
+                <strong>{currentRow.id}</strong>. <br />
+                This action cannot be undone.
+              </>
+            }
+            confirmText='Delete'
+          />
+        </>
+      )}
+    </>
+  )
+}

+ 21 - 0
src/features/tasks/components/tasks-primary-buttons.tsx

@@ -0,0 +1,21 @@
+import { IconDownload, IconPlus } from '@tabler/icons-react'
+import { Button } from '@/components/ui/button'
+import { useTasks } from '../context/tasks-context'
+
+export function TasksPrimaryButtons() {
+  const { setOpen } = useTasks()
+  return (
+    <div className='flex gap-2'>
+      <Button
+        variant='outline'
+        className='space-x-1'
+        onClick={() => setOpen('import')}
+      >
+        <span>Import</span> <IconDownload size={18} />
+      </Button>
+      <Button className='space-x-1' onClick={() => setOpen('create')}>
+        <span>Create</span> <IconPlus size={18} />
+      </Button>
+    </div>
+  )
+}

+ 13 - 9
src/features/tasks/context/tasks-context.tsx

@@ -1,7 +1,8 @@
-import React from 'react'
+import React, { useState } from 'react'
+import useDialogState from '@/hooks/use-dialog-state'
 import { Task } from '../data/schema'
 
-export type TasksDialogType = 'create' | 'update' | 'delete' | 'import'
+type TasksDialogType = 'create' | 'update' | 'delete' | 'import'
 
 interface TasksContextType {
   open: TasksDialogType | null
@@ -14,21 +15,24 @@ const TasksContext = React.createContext<TasksContextType | null>(null)
 
 interface Props {
   children: React.ReactNode
-  value: TasksContextType
 }
 
-export default function TasksContextProvider({ children, value }: Props) {
-  return <TasksContext.Provider value={value}>{children}</TasksContext.Provider>
+export default function TasksProvider({ children }: Props) {
+  const [open, setOpen] = useDialogState<TasksDialogType>(null)
+  const [currentRow, setCurrentRow] = useState<Task | null>(null)
+  return (
+    <TasksContext value={{ open, setOpen, currentRow, setCurrentRow }}>
+      {children}
+    </TasksContext>
+  )
 }
 
 // eslint-disable-next-line react-refresh/only-export-components
-export const useTasksContext = () => {
+export const useTasks = () => {
   const tasksContext = React.useContext(TasksContext)
 
   if (!tasksContext) {
-    throw new Error(
-      'useTasksContext has to be used within <TasksContext.Provider>'
-    )
+    throw new Error('useTasks has to be used within <TasksContext>')
   }
 
   return tasksContext

+ 7 - 94
src/features/tasks/index.tsx

@@ -1,9 +1,3 @@
-import { useState } from 'react'
-import { IconDownload, IconPlus } from '@tabler/icons-react'
-import useDialogState from '@/hooks/use-dialog-state'
-import { toast } from '@/hooks/use-toast'
-import { Button } from '@/components/ui/button'
-import { ConfirmDialog } from '@/components/confirm-dialog'
 import { Header } from '@/components/layout/header'
 import { Main } from '@/components/layout/main'
 import { ProfileDropdown } from '@/components/profile-dropdown'
@@ -11,20 +5,14 @@ import { Search } from '@/components/search'
 import { ThemeSwitch } from '@/components/theme-switch'
 import { columns } from './components/columns'
 import { DataTable } from './components/data-table'
-import { TasksImportDialog } from './components/tasks-import-dialog'
-import { TasksMutateDrawer } from './components/tasks-mutate-drawer'
-import TasksContextProvider, { TasksDialogType } from './context/tasks-context'
-import { Task } from './data/schema'
+import { TasksDialogs } from './components/tasks-dialogs'
+import { TasksPrimaryButtons } from './components/tasks-primary-buttons'
+import TasksProvider from './context/tasks-context'
 import { tasks } from './data/tasks'
 
 export default function Tasks() {
-  // Local states
-  const [currentRow, setCurrentRow] = useState<Task | null>(null)
-  const [open, setOpen] = useDialogState<TasksDialogType>(null)
-
   return (
-    <TasksContextProvider value={{ open, setOpen, currentRow, setCurrentRow }}>
-      {/* ===== Top Heading ===== */}
+    <TasksProvider>
       <Header fixed>
         <Search />
         <div className='ml-auto flex items-center space-x-4'>
@@ -41,89 +29,14 @@ export default function Tasks() {
               Here&apos;s a list of your tasks for this month!
             </p>
           </div>
-          <div className='flex gap-2'>
-            <Button
-              variant='outline'
-              className='space-x-1'
-              onClick={() => setOpen('import')}
-            >
-              <span>Import</span> <IconDownload size={18} />
-            </Button>
-            <Button className='space-x-1' onClick={() => setOpen('create')}>
-              <span>Create</span> <IconPlus size={18} />
-            </Button>
-          </div>
+          <TasksPrimaryButtons />
         </div>
         <div className='-mx-4 flex-1 overflow-auto px-4 py-1 lg:flex-row lg:space-x-12 lg:space-y-0'>
           <DataTable data={tasks} columns={columns} />
         </div>
       </Main>
 
-      <TasksMutateDrawer
-        key='task-create'
-        open={open === 'create'}
-        onOpenChange={() => setOpen('create')}
-      />
-
-      <TasksImportDialog
-        key='tasks-import'
-        open={open === 'import'}
-        onOpenChange={() => setOpen('import')}
-      />
-
-      {currentRow && (
-        <>
-          <TasksMutateDrawer
-            key={`task-update-${currentRow.id}`}
-            open={open === 'update'}
-            onOpenChange={() => {
-              setOpen('update')
-              setTimeout(() => {
-                setCurrentRow(null)
-              }, 500)
-            }}
-            currentRow={currentRow}
-          />
-
-          <ConfirmDialog
-            key='task-delete'
-            destructive
-            open={open === 'delete'}
-            onOpenChange={() => {
-              setOpen('delete')
-              setTimeout(() => {
-                setCurrentRow(null)
-              }, 500)
-            }}
-            handleConfirm={() => {
-              setOpen(null)
-              setTimeout(() => {
-                setCurrentRow(null)
-              }, 500)
-              toast({
-                title: 'The following task has been deleted:',
-                description: (
-                  <pre className='mt-2 w-[340px] rounded-md bg-slate-950 p-4'>
-                    <code className='text-white'>
-                      {JSON.stringify(currentRow, null, 2)}
-                    </code>
-                  </pre>
-                ),
-              })
-            }}
-            className='max-w-md'
-            title={`Delete this task: ${currentRow.id} ?`}
-            desc={
-              <>
-                You are about to delete a task with the ID{' '}
-                <strong>{currentRow.id}</strong>. <br />
-                This action cannot be undone.
-              </>
-            }
-            confirmText='Delete'
-          />
-        </>
-      )}
-    </TasksContextProvider>
+      <TasksDialogs />
+    </TasksProvider>
   )
 }

+ 2 - 2
src/features/users/components/data-table-row-actions.tsx

@@ -10,7 +10,7 @@ import {
   DropdownMenuShortcut,
   DropdownMenuTrigger,
 } from '@/components/ui/dropdown-menu'
-import { useUsersContext } from '../context/users-context'
+import { useUsers } from '../context/users-context'
 import { User } from '../data/schema'
 
 interface DataTableRowActionsProps {
@@ -18,7 +18,7 @@ interface DataTableRowActionsProps {
 }
 
 export function DataTableRowActions({ row }: DataTableRowActionsProps) {
-  const { setOpen, setCurrentRow } = useUsersContext()
+  const { setOpen, setCurrentRow } = useUsers()
   return (
     <>
       <DropdownMenu modal={false}>

+ 51 - 0
src/features/users/components/users-dialogs.tsx

@@ -0,0 +1,51 @@
+import { useUsers } from '../context/users-context'
+import { UsersActionDialog } from './users-action-dialog'
+import { UsersDeleteDialog } from './users-delete-dialog'
+import { UsersInviteDialog } from './users-invite-dialog'
+
+export function UsersDialogs() {
+  const { open, setOpen, currentRow, setCurrentRow } = useUsers()
+  return (
+    <>
+      <UsersActionDialog
+        key='user-add'
+        open={open === 'add'}
+        onOpenChange={() => setOpen('add')}
+      />
+
+      <UsersInviteDialog
+        key='user-invite'
+        open={open === 'invite'}
+        onOpenChange={() => setOpen('invite')}
+      />
+
+      {currentRow && (
+        <>
+          <UsersActionDialog
+            key={`user-edit-${currentRow.id}`}
+            open={open === 'edit'}
+            onOpenChange={() => {
+              setOpen('edit')
+              setTimeout(() => {
+                setCurrentRow(null)
+              }, 500)
+            }}
+            currentRow={currentRow}
+          />
+
+          <UsersDeleteDialog
+            key={`user-delete-${currentRow.id}`}
+            open={open === 'delete'}
+            onOpenChange={() => {
+              setOpen('delete')
+              setTimeout(() => {
+                setCurrentRow(null)
+              }, 500)
+            }}
+            currentRow={currentRow}
+          />
+        </>
+      )}
+    </>
+  )
+}

+ 21 - 0
src/features/users/components/users-primary-buttons.tsx

@@ -0,0 +1,21 @@
+import { IconMailPlus, IconUserPlus } from '@tabler/icons-react'
+import { Button } from '@/components/ui/button'
+import { useUsers } from '../context/users-context'
+
+export function UsersPrimaryButtons() {
+  const { setOpen } = useUsers()
+  return (
+    <div className='flex gap-2'>
+      <Button
+        variant='outline'
+        className='space-x-1'
+        onClick={() => setOpen('invite')}
+      >
+        <span>Invite User</span> <IconMailPlus size={18} />
+      </Button>
+      <Button className='space-x-1' onClick={() => setOpen('add')}>
+        <span>Add User</span> <IconUserPlus size={18} />
+      </Button>
+    </div>
+  )
+}

+ 14 - 9
src/features/users/context/users-context.tsx

@@ -1,7 +1,8 @@
-import React from 'react'
+import React, { useState } from 'react'
+import useDialogState from '@/hooks/use-dialog-state'
 import { User } from '../data/schema'
 
-export type UsersDialogType = 'invite' | 'add' | 'edit' | 'delete'
+type UsersDialogType = 'invite' | 'add' | 'edit' | 'delete'
 
 interface UsersContextType {
   open: UsersDialogType | null
@@ -14,21 +15,25 @@ const UsersContext = React.createContext<UsersContextType | null>(null)
 
 interface Props {
   children: React.ReactNode
-  value: UsersContextType
 }
 
-export default function UsersContextProvider({ children, value }: Props) {
-  return <UsersContext.Provider value={value}>{children}</UsersContext.Provider>
+export default function UsersProvider({ children }: Props) {
+  const [open, setOpen] = useDialogState<UsersDialogType>(null)
+  const [currentRow, setCurrentRow] = useState<User | null>(null)
+
+  return (
+    <UsersContext value={{ open, setOpen, currentRow, setCurrentRow }}>
+      {children}
+    </UsersContext>
+  )
 }
 
 // eslint-disable-next-line react-refresh/only-export-components
-export const useUsersContext = () => {
+export const useUsers = () => {
   const usersContext = React.useContext(UsersContext)
 
   if (!usersContext) {
-    throw new Error(
-      'useUsersContext has to be used within <UsersContext.Provider>'
-    )
+    throw new Error('useUsers has to be used within <UsersContext>')
   }
 
   return usersContext

+ 8 - 69
src/features/users/index.tsx

@@ -1,34 +1,22 @@
-import { useState } from 'react'
-import { IconMailPlus, IconUserPlus } from '@tabler/icons-react'
-import useDialogState from '@/hooks/use-dialog-state'
-import { Button } from '@/components/ui/button'
 import { Header } from '@/components/layout/header'
 import { Main } from '@/components/layout/main'
 import { ProfileDropdown } from '@/components/profile-dropdown'
 import { Search } from '@/components/search'
 import { ThemeSwitch } from '@/components/theme-switch'
-import { UsersActionDialog } from './components/users-action-dialog'
 import { columns } from './components/users-columns'
-import { UsersDeleteDialog } from './components/users-delete-dialog'
-import { UsersInviteDialog } from './components/users-invite-dialog'
+import { UsersDialogs } from './components/users-dialogs'
+import { UsersPrimaryButtons } from './components/users-primary-buttons'
 import { UsersTable } from './components/users-table'
-import UsersContextProvider, {
-  type UsersDialogType,
-} from './context/users-context'
-import { User, userListSchema } from './data/schema'
+import UsersProvider from './context/users-context'
+import { userListSchema } from './data/schema'
 import { users } from './data/users'
 
 export default function Users() {
-  // Dialog states
-  const [currentRow, setCurrentRow] = useState<User | null>(null)
-  const [open, setOpen] = useDialogState<UsersDialogType>(null)
-
   // Parse user list
   const userList = userListSchema.parse(users)
 
   return (
-    <UsersContextProvider value={{ open, setOpen, currentRow, setCurrentRow }}>
-      {/* ===== Top Heading ===== */}
+    <UsersProvider>
       <Header fixed>
         <Search />
         <div className='ml-auto flex items-center space-x-4'>
@@ -45,63 +33,14 @@ export default function Users() {
               Manage your users and their roles here.
             </p>
           </div>
-          <div className='flex gap-2'>
-            <Button
-              variant='outline'
-              className='space-x-1'
-              onClick={() => setOpen('invite')}
-            >
-              <span>Invite User</span> <IconMailPlus size={18} />
-            </Button>
-            <Button className='space-x-1' onClick={() => setOpen('add')}>
-              <span>Add User</span> <IconUserPlus size={18} />
-            </Button>
-          </div>
+          <UsersPrimaryButtons />
         </div>
         <div className='-mx-4 flex-1 overflow-auto px-4 py-1 lg:flex-row lg:space-x-12 lg:space-y-0'>
           <UsersTable data={userList} columns={columns} />
         </div>
       </Main>
 
-      <UsersActionDialog
-        key='user-add'
-        open={open === 'add'}
-        onOpenChange={() => setOpen('add')}
-      />
-
-      <UsersInviteDialog
-        key='user-invite'
-        open={open === 'invite'}
-        onOpenChange={() => setOpen('invite')}
-      />
-
-      {currentRow && (
-        <>
-          <UsersActionDialog
-            key={`user-edit-${currentRow.id}`}
-            open={open === 'edit'}
-            onOpenChange={() => {
-              setOpen('edit')
-              setTimeout(() => {
-                setCurrentRow(null)
-              }, 500)
-            }}
-            currentRow={currentRow}
-          />
-
-          <UsersDeleteDialog
-            key={`user-delete-${currentRow.id}`}
-            open={open === 'delete'}
-            onOpenChange={() => {
-              setOpen('delete')
-              setTimeout(() => {
-                setCurrentRow(null)
-              }, 500)
-            }}
-            currentRow={currentRow}
-          />
-        </>
-      )}
-    </UsersContextProvider>
+      <UsersDialogs />
+    </UsersProvider>
   )
 }