From feae2a28b0546d88b0c29826eb62c66284a871be Mon Sep 17 00:00:00 2001 From: Nishit Suwal <81785002+NSUWAL123@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:26:23 +0545 Subject: [PATCH] feat(frontend): submission table date range filter (#2091) * fix(ISubmissions): update ts type * fix(customDatePicker): add start and end date range * fix(submissionTable): update submitted date with dateRange * fix(customDatePicker): update component name * fix(submissionsTable): pass query strings as object instead of static string * fix(task): update getDownloadProjectSubmission service function * fix(submissionsTable): update initial filter state, clear dateRange on filter clear * fix(projectSubmissionsSkeletonLoader): fix index clash error * fix(submissionsTable): remove useEffect block calculating no of filters applied * fix(select): update ts type * fix(submissionsTable): fallback to empty string if not values present on custom select * fix(index): update datepicker css --- src/frontend/src/api/task.ts | 17 ++--- .../ProjectSubmissionsSkeletonLoader.tsx | 4 +- .../ProjectSubmissions/SubmissionsTable.tsx | 72 +++++++++---------- ...stomDatePicker.tsx => DateRangePicker.tsx} | 24 +++++-- src/frontend/src/components/common/Select.tsx | 2 +- src/frontend/src/index.css | 12 ++-- src/frontend/src/store/types/ISubmissions.ts | 2 +- 7 files changed, 73 insertions(+), 60 deletions(-) rename src/frontend/src/components/common/{CustomDatePicker.tsx => DateRangePicker.tsx} (51%) diff --git a/src/frontend/src/api/task.ts b/src/frontend/src/api/task.ts index 5f2a7dabed..c77f0111b5 100644 --- a/src/frontend/src/api/task.ts +++ b/src/frontend/src/api/task.ts @@ -2,11 +2,14 @@ import { AppDispatch } from '@/store/Store'; import CoreModules from '@/shared/CoreModules'; import { TaskActions } from '@/store/slices/TaskSlice'; -export const getDownloadProjectSubmission = (url: string, projectName: string) => { +export const getDownloadProjectSubmission = ( + url: string, + projectName: string, + params: { project_id: string; export_json: boolean; submitted_date_range: string | null }, +) => { return async (dispatch: AppDispatch) => { - const params = new URLSearchParams(url.split('?')[1]); - const isExportJson = params.get('export_json'); - const isJsonOrCsv = isExportJson === 'true' ? 'json' : 'csv'; + const isExportJson = params.export_json; + const isJsonOrCsv = isExportJson ? 'json' : 'csv'; dispatch( TaskActions.GetDownloadProjectSubmissionLoading({ type: isJsonOrCsv, @@ -16,12 +19,10 @@ export const getDownloadProjectSubmission = (url: string, projectName: string) = const getProjectSubmission = async (url: string) => { try { - const response = await CoreModules.axios.get(url, { - responseType: 'blob', - }); + const response = await CoreModules.axios.get(url, { params, responseType: 'blob' }); var a = document.createElement('a'); a.href = window.URL.createObjectURL(response.data); - a.download = isExportJson === 'true' ? `${projectName}.json` : `${projectName}.zip`; + a.download = isExportJson ? `${projectName}.json` : `${projectName}.zip`; a.click(); } catch (error) { } finally { diff --git a/src/frontend/src/components/ProjectSubmissions/ProjectSubmissionsSkeletonLoader.tsx b/src/frontend/src/components/ProjectSubmissions/ProjectSubmissionsSkeletonLoader.tsx index b143545f1b..9912f1e918 100644 --- a/src/frontend/src/components/ProjectSubmissions/ProjectSubmissionsSkeletonLoader.tsx +++ b/src/frontend/src/components/ProjectSubmissions/ProjectSubmissionsSkeletonLoader.tsx @@ -29,8 +29,8 @@ export const SubmissionsTableSkeletonLoader = () => { {Array.from({ length: 10 }).map((_, i) => (
- {Array.from({ length: 15 }).map(() => ( - + {Array.from({ length: 15 }).map((_, ind) => ( + ))}
))} diff --git a/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx b/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx index 21b6d09675..77798be1ec 100644 --- a/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx +++ b/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx @@ -11,7 +11,7 @@ import windowDimention from '@/hooks/WindowDimension'; import Button from '@/components/common/Button'; import { Modal } from '@/components/common/Modal'; import { CustomSelect } from '@/components/common/Select.js'; -import CustomDatePicker from '@/components/common/CustomDatePicker'; +import DateRangePicker from '@/components/common/DateRangePicker'; import Table, { TableHeader } from '@/components/common/CustomTable'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/common/Dropdown'; import { SubmissionsTableSkeletonLoader } from '@/components/ProjectSubmissions/ProjectSubmissionsSkeletonLoader.js'; @@ -39,7 +39,7 @@ const SubmissionsTable = ({ toggleView }) => { task_id: searchParams.get('task_id') ? searchParams?.get('task_id') || null : null, submitted_by: searchParams.get('submitted_by'), review_state: searchParams.get('review_state'), - submitted_date: searchParams.get('submitted_date'), + submitted_date_range: searchParams.get('submitted_date_range'), }; const [filter, setFilter] = useState(initialFilterState); @@ -70,20 +70,25 @@ const SubmissionsTable = ({ toggleView }) => { }; const taskList = projectData[projectIndex]?.taskBoundries; - const [numberOfFilters, setNumberOfFilters] = useState(0); const [paginationPage, setPaginationPage] = useState(1); const [submittedBy, setSubmittedBy] = useState(null); + const [dateRange, setDateRange] = useState<{ start: Date | null; end: Date | null }>({ + start: initialFilterState?.submitted_date_range + ? new Date(initialFilterState.submitted_date_range.split(',')[0]) + : null, + end: initialFilterState?.submitted_date_range + ? new Date(initialFilterState.submitted_date_range.split(',')[1]) + : null, + }); useEffect(() => { - let count = 0; - const filters = Object.keys(filter); - filters?.map((fltr) => { - if (filter[fltr]) { - count = count + 1; - } - }); - setNumberOfFilters(count); - }, [filter]); + if (!dateRange.start || !dateRange.end) return; + + setFilter((prev) => ({ + ...prev, + submitted_date_range: `${format(new Date(dateRange.start as Date), 'yyyy-MM-dd')},${format(new Date(dateRange.end as Date), 'yyyy-MM-dd')}`, + })); + }, [dateRange]); const updatedSubmissionFormFields = submissionFormFields //filter necessary fields only @@ -163,7 +168,8 @@ const SubmissionsTable = ({ toggleView }) => { const clearFilters = () => { setSearchParams({ tab: 'table' }); - setFilter({ task_id: null, submitted_by: null, review_state: null, submitted_date: null }); + setFilter({ task_id: null, submitted_by: null, review_state: null, submitted_date_range: null }); + setDateRange({ start: null, end: null }); }; function getValueByPath(obj: any, path: string) { @@ -203,21 +209,13 @@ const SubmissionsTable = ({ toggleView }) => { }; const handleDownload = (downloadType: 'csv' | 'json') => { - if (downloadType === 'csv') { - dispatch( - getDownloadProjectSubmission( - `${import.meta.env.VITE_API_URL}/submission/download?project_id=${projectId}&export_json=false`, - projectInfo.name!, - ), - ); - } else if (downloadType === 'json') { - dispatch( - getDownloadProjectSubmission( - `${import.meta.env.VITE_API_URL}/submission/download?project_id=${projectId}&export_json=true`, - projectInfo.name!, - ), - ); - } + dispatch( + getDownloadProjectSubmission(`${import.meta.env.VITE_API_URL}/submission/download`, projectInfo.name!, { + project_id: projectId, + submitted_date_range: filter?.submitted_date_range, + export_json: downloadType === 'json', + }), + ); }; const handleTaskMap = async () => { @@ -270,7 +268,7 @@ const SubmissionsTable = ({ toggleView }) => { >

FILTER

{' '}
-

{numberOfFilters}

+

{Object.values(filter).filter((filterObjValue) => filterObjValue).length}

@@ -284,7 +282,7 @@ const SubmissionsTable = ({ toggleView }) => { placeholder="Select" data={taskInfo} dataKey="value" - value={filter?.task_id?.toString() || undefined} + value={filter?.task_id?.toString() || ''} valueKey="task_id" label="task_id" onValueChange={(value) => value && setFilter((prev) => ({ ...prev, task_id: value.toString() }))} @@ -297,7 +295,7 @@ const SubmissionsTable = ({ toggleView }) => { placeholder="Select" data={reviewStateData} dataKey="value" - value={filter?.review_state || undefined} + value={filter?.review_state || ''} valueKey="value" label="label" onValueChange={(value) => @@ -307,13 +305,13 @@ const SubmissionsTable = ({ toggleView }) => { className="fmtm-text-grey-700 fmtm-text-sm !fmtm-mb-0 fmtm-bg-white" /> -
- + - setFilter((prev) => ({ ...prev, submitted_date: format(new Date(date), 'yyyy-MM-dd') })) - } + startDate={dateRange?.start} + endDate={dateRange?.end} + setStartDate={(date) => setDateRange((prev) => ({ ...prev, start: date }))} + setEndDate={(date) => setDateRange((prev) => ({ ...prev, end: date }))} className="fmtm-text-grey-700 fmtm-text-sm !fmtm-mb-0 fmtm-w-full" />
diff --git a/src/frontend/src/components/common/CustomDatePicker.tsx b/src/frontend/src/components/common/DateRangePicker.tsx similarity index 51% rename from src/frontend/src/components/common/CustomDatePicker.tsx rename to src/frontend/src/components/common/DateRangePicker.tsx index a83e524b35..4320539eeb 100644 --- a/src/frontend/src/components/common/CustomDatePicker.tsx +++ b/src/frontend/src/components/common/DateRangePicker.tsx @@ -2,22 +2,32 @@ import React from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; -type CustomDatePickerType = { +type DateRangePickerType = { title: string; className: string; - selectedDate: string | null; - setSelectedDate: (date: Date) => void; + setStartDate: (date: Date | null) => void; + setEndDate: (date: Date | null) => void; + startDate: Date | null; + endDate: Date | null; }; -const CustomDatePicker = ({ title, className, selectedDate, setSelectedDate }: CustomDatePickerType) => { +const DateRangePicker = ({ title, className, setStartDate, setEndDate, startDate, endDate }: DateRangePickerType) => { + const onChange = (dates: [Date | null, Date | null]) => { + const [start, end] = dates; + setStartDate(start); + setEndDate(end); + }; return (
{title && (

{title}

)} setSelectedDate(date)} + selected={startDate} + onChange={onChange} + startDate={startDate} + endDate={endDate} + selectsRange className="fmtm-outline-none fmtm-border-[1px] fmtm-border-gray-300 fmtm-h-[2rem] fmtm-w-full fmtm-z-50 fmtm-px-2 fmtm-text-base fmtm-pt-1 hover" placeholderText="YYYY/MM/DD" dateFormat="yyyy/MM/dd" @@ -26,4 +36,4 @@ const CustomDatePicker = ({ title, className, selectedDate, setSelectedDate }: C ); }; -export default CustomDatePicker; +export default DateRangePicker; diff --git a/src/frontend/src/components/common/Select.tsx b/src/frontend/src/components/common/Select.tsx index 7adbc14d42..074aad8070 100644 --- a/src/frontend/src/components/common/Select.tsx +++ b/src/frontend/src/components/common/Select.tsx @@ -97,7 +97,7 @@ interface ICustomSelect { placeholder: string; data: any; dataKey: string; - value?: string | undefined; + value?: string; valueKey: string; label: string; onValueChange?: (value: string | null | number) => void; diff --git a/src/frontend/src/index.css b/src/frontend/src/index.css index 067a7d4d57..f97aa1a9a8 100755 --- a/src/frontend/src/index.css +++ b/src/frontend/src/index.css @@ -247,12 +247,16 @@ button { .react-datepicker-popper { z-index: 9000; } -.react-datepicker__day--selected { - background-color: #d73f3e; +.react-datepicker__day--selected, +.react-datepicker__day--in-range { + background-color: #d73f3e !important; +} +.react-datepicker__day--in-selecting-range { + background-color: #df6565 !important; } .react-datepicker__day--selected:hover { - background-color: #bc2c2c; + background-color: #bc2c2c !important; } .react-datepicker__day--keyboard-selected { - background-color: #fcc2c2; + background-color: #fcc2c2 !important; } diff --git a/src/frontend/src/store/types/ISubmissions.ts b/src/frontend/src/store/types/ISubmissions.ts index 0baecd7a12..ed0577e47e 100644 --- a/src/frontend/src/store/types/ISubmissions.ts +++ b/src/frontend/src/store/types/ISubmissions.ts @@ -35,7 +35,7 @@ export type filterType = { task_id: string | null; submitted_by: string | null; review_state: string | null; - submitted_date: string | null; + submitted_date_range: string | null; }; type mappedVsValidatedTaskType = {