Styled Table Cell - Copy this React, Mui Component to your project
Generate a React component for managing leave applications with the following features: Reapply Leave Action: Add an action menu for each leave in the LeaveApplicationsList component. Include an option to "Reapply Leave" if there is a mistake in a previously submitted application (either in "Pending" or "Approved" status). On selecting "Reapply Leave," allow users to modify the details (subject, reason, start/end date). Call the reschedule API endpoint to update the application. Change the status of the leave to "Reapplied." API Integration: Use the existing API helper for submitting the reschedule API call. Update the leave's status to "Reapplied" and reload the leave list. Admin Workflow: Ensure the leave marked as "Reapplied" can be approved or rejected by an admin later. UI Updates: Extend the LeaveApplicationsList component to show an action menu with "Reapply Leave" and other options. Highlight leaves in the "Reapplied" state for clarity. Reusable Form: Reuse the leave request form for editing during the reapply flow, pre filling it with existing leave details. Error Handling and Feedback: Show appropriate success/error notifications during the reapply process using the toast library. Responsive and Accessible Design: Ensure the UI is fully responsive and adheres to accessibility best practices. Here's the existing codebase for context. Adapt and integrate this functionality efficiently. // ** React Imports import React, { useState, useEffect } from 'react' // ** MUI Imports import { Box, TextField, Typography, useTheme } from '@mui/material' import Button from '@mui/material/Button' // ** Third Partu Imports import toast from 'react hot toast' import { useForm, SubmitHandler, Controller } from 'react hook form' import { LocalizationProvider } from '@mui/x date pickers/LocalizationProvider' import { DateTimePicker as _DateTimePicker } from '@mui/x date pickers/DateTimePicker' import { AdapterDateFns } from '@mui/x date pickers/AdapterDateFnsV3' import { ColumnFiltersState } from '@tanstack/react table' import styled from '@emotion/styled' // ** Components Imports import LeaveApplicationsList from 'src/views/pages/leaves/LeavesList' // ** Hooks Imports import useDebounce from 'src/hooks/useDebounce' // ** Helper Imports import apiHelper from 'src/@core/utils/api helper' import { transformLeaveApplicationsResponse } from 'src/@core/support/transform responses' // ** Config Imports import { endpointConfig } from 'src/configs/default' // ** Types Imports import { LeaveRequest } from 'src/@core/support/types' interface FormInputs { subject: string reason: string startDateTime: Date | null endDateTime: Date | null } const DateTimePicker = styled(_DateTimePicker)(() => ({ width: '100%', '& .MuiOutlinedInput root': { borderRadius: 8, fontSize: 16 }, '& .MuiOutlinedInput notchedOutline': { borderWidth: 2, borderStyle: 'solid' } })) const RequestLeave = () => { const theme = useTheme() const { register, handleSubmit, formState: { errors, isValid, isSubmitting }, reset, control } = useForm<FormInputs>({ mode: 'onChange', // Validates as inputs change reValidateMode: 'onChange' // Revalidates on change }) const [startDateTime, setStartDateTime] = useState<Date | null>(null) const [endDateTime, setEndDateTime] = useState<Date | null>(null) const [leaveApplications, setLeaveApplications] = useState<LeaveRequest[]>([]) const [filteredLeaveApplications, setFilteredLeaveApplications] = useState<LeaveRequest[]>([]) const [totalCount, setTotalCount] = useState(0) const [currentPage, setCurrentPage] = useState(1) const [pageSize, setPageSize] = useState(10) const [searchQuery, setSearchQuery] = useState<ColumnFiltersState>([]) const [loading, setLoading] = useState(false) // Debounced value for the search query const debouncedSearchQuery = useDebounce(searchQuery, 500) const isDisabledField = !isValid || // Form is not valid isSubmitting || // Form is being submitted loading || // Data is loading !startDateTime || // Start date is not selected !endDateTime // End date is not selected const onSubmit: SubmitHandler<FormInputs> = async data => { try { const response = await apiHelper.post(endpointConfig.user + `/request leave`, data) if (response.status === 200) { toast.success('Leave request submitted successfully', { duration: 2000 }) setStartDateTime(null) setEndDateTime(null) reset({ subject: '', reason: '', startDateTime: null, endDateTime: null }) await fetchLeaveApplications() } } catch (error) { console.error('Error:', error) toast.error('Failed to process request', { duration: 2000 }) } } async function fetchLeaveApplications() { try { setLoading(true) const response = await apiHelper.get( `${endpointConfig.user}/leave/applications?limit=${pageSize}&page=${currentPage}` ) if (response.status === 200) { const transformedResponse = await transformLeaveApplicationsResponse(response.data.leaveRequests) setLeaveApplications(transformedResponse.leaveRequests) setTotalCount(response.data.totalCount) } } catch (error) { console.error('Error fetching leave applications:', error) toast.error('Failed to fetch leave applications.') } finally { setLoading(false) } } async function fetchSearchResults() { const queryParam = debouncedSearchQuery .map(filter => `${filter.id}=${encodeURIComponent(filter.value as string)}`) .join('&') if (!queryParam && pageSize === 10 && currentPage === 1) return try { const response = await apiHelper.get( `${endpointConfig.user}/leave/applications?limit=${pageSize}&page=${currentPage}&${queryParam}` ) if (response.status === 200) { const transformedResponse = await transformLeaveApplicationsResponse(response.data.leaveRequests) setFilteredLeaveApplications(transformedResponse.leaveRequests) setTotalCount(response.data.totalCount) } } catch (error) { console.error('Error fetching leave applications:', error) toast.error('An error occurred while trying to fetch search results.') } } useEffect(() => { fetchLeaveApplications() return () => { // Reset the state on unmount setLoading(false) setSearchQuery([]) setFilteredLeaveApplications([]) } // eslint disable next line react hooks/exhaustive deps }, []) useEffect(() => { if (debouncedSearchQuery) fetchSearchResults() return () => { // Reset the state on unmount setFilteredLeaveApplications([]) } // eslint disable next line react hooks/exhaustive deps }, [currentPage, pageSize, debouncedSearchQuery]) return ( <> <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', maxWidth: '900px', margin: '0 auto', padding: '2rem', borderRadius: '8px', boxShadow: '0 4px 12px rgba(0,0,0,0.1)', backgroundColor: theme.palette.background.paper }} > <Typography variant='h4' gutterBottom> Request Leave </Typography> <Box component='form' onSubmit={handleSubmit(onSubmit)} style={{ width: '100%' }}> {/* Subject */} <TextField fullWidth id='subject' label='Subject' variant='outlined' sx={{ marginBottom: 3 }} {...register('subject', { required: 'Subject is required' })} error={!!errors.reason} helperText={errors.subject?.message as string} /> {/* Reason for Leave */} <TextField fullWidth multiline rows={4} id='reason' label='Reason for Leave' variant='outlined' sx={{ marginBottom: 3 }} {...register('reason', { required: 'Reason is required' })} error={!!errors.reason} helperText={errors.reason?.message as string} /> {/* Date Range Picker */} <Box sx={{ marginBottom: 3, display: 'flex', justifyContent: 'space between', gap: 3 }} > <LocalizationProvider dateAdapter={AdapterDateFns}> <Box sx={{ width: '100%' }}> <Controller name='startDateTime' control={control} defaultValue={null} rules={{ required: 'Start date and time is required', validate: value => value && endDateTime ? new Date(value).getTime() < new Date(endDateTime).getTime() ? true : 'Start time must be before end time' : true }} render={({ field }) => ( <DateTimePicker {...field} label='Start Date & Time' value={startDateTime} onChange={newValue => { setStartDateTime(newValue) field.onChange(newValue) }} disablePast /> )} /> {errors.startDateTime && <Typography color='error'>{errors.startDateTime.message}</Typography>} </Box> <Box sx={{ width: '100%' }}> <Controller name='endDateTime' control={control} defaultValue={null} rules={{ required: 'End date and time is required', validate: value => value && startDateTime ? new Date(value).getTime() > new Date(startDateTime).getTime() ? true : 'End time must be after start time' : true }} render={({ field }) => ( <DateTimePicker {...field} label='End Date & Time' value={endDateTime} onChange={newValue => { setEndDateTime(newValue) field.onChange(newValue) }} disablePast /> )} /> {errors.endDateTime && <Typography color='error'>{errors.endDateTime.message}</Typography>} </Box> </LocalizationProvider> </Box> {/* Submit Button */} <Button type='submit' variant='contained' color='primary' fullWidth sx={{ padding: '10px 0', cursor: !isValid || loading ? 'not allowed !important' : 'pointer', pointerEvents: 'all !important', backgroundColor: !isValid || loading ? 'grey.400' : 'primary.main', color: !isValid || loading ? 'grey.600' : 'white' }} disabled={isDisabledField} > Submit Leave Request </Button> </Box> </Box> <LeaveApplicationsList title='Past Applied Leaves' isLoading={loading} data={leaveApplications} filteredData={filteredLeaveApplications} currentPage={currentPage} pageSize={pageSize} searchQuery={searchQuery} totalCount={totalCount} onFilterChange={setSearchQuery} onPageChange={setCurrentPage} onPageSizeChange={setPageSize} /> </> ) } RequestLeave.acl = { action: 'read', subject: 'client page' } export default RequestLeave and list: // ** React Imports import React, { useMemo, Suspense } from 'react' // ** MUI Imports import { Box, Typography } from '@mui/material' // ** Third Party Imports import { ColumnDef, ColumnFiltersState, OnChangeFn } from '@tanstack/react table' // ** Components Imports import Chip from 'src/@core/components/mui/chip' import LeaveApplicationsDataTable from 'src/@core/components/table/leave applications' import FallbackSpinner from 'src/@core/components/spinner' // ** Types Imports import { LeaveRequest } from 'src/@core/support/types' interface LeaveApplicationsListProps { title: string isLoading: boolean data: LeaveRequest[] filteredData: LeaveRequest[] totalCount: number currentPage: number pageSize: number searchQuery: ColumnFiltersState onFilterChange: OnChangeFn<ColumnFiltersState> onPageChange: (page: number) => void onPageSizeChange: (pageSize: number) => void } const LeaveApplicationsList: React.FC<LeaveApplicationsListProps> = props => { const { title, isLoading, data, filteredData, totalCount, currentPage, pageSize, searchQuery, onFilterChange, onPageChange, onPageSizeChange } = props const columns = useMemo<ColumnDef<LeaveRequest>[]>( () => [ { accessorKey: 'subject' }, { accessorKey: 'reason' }, { accessorKey: 'status', header: 'Status', cell: info => ( <Chip skin='light' label={(info.getValue() as string).toUpperCase()} color={info.getValue() === 'approved' ? 'success' : info.getValue() === 'rejected' ? 'error' : 'warning'} variant='outlined' /> ) }, { accessorKey: 'dateRange', header: 'Date Range', enableColumnFilter: false, cell: info => ( <span> {(info.getValue() as unknown as any).start} {(info.getValue() as unknown as any).end} </span> ) } ], [] ) // const memorizedData = useMemo(() => [...data, ...filteredData], [data, filteredData]) if (isLoading) return <>Loading...</> return ( <Suspense fallback={<FallbackSpinner />}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 12 }}> <Typography variant='h5' textAlign='center' gutterBottom> {title} </Typography> {/* {filteredData.length > 0 ? ( */} <LeaveApplicationsDataTable columns={columns} data={filteredData.length ? filteredData : data} totalCount={totalCount} currentPage={currentPage} pageSize={pageSize} columnFilters={searchQuery} onPageChange={onPageChange} onPageSizeChange={onPageSizeChange} onColumnFilterChange={onFilterChange} /> {/* ) : ( <Typography variant='body2'>No leave applications found.</Typography> )} */} </Box> </Suspense> ) } export default LeaveApplicationsList
