From 969fe4c8b0847cb9856eaa81a3e26a234ccb0e92 Mon Sep 17 00:00:00 2001 From: Miguel Date: Thu, 31 Oct 2024 16:19:39 +0800 Subject: [PATCH 1/2] fixed checking errors --- api/Schema/Queries/TimeSheetQuery.cs | 4 +- api/Services/TimeSheetService.cs | 14 +- client/package-lock.json | 121 ++++++++++++++++- client/package.json | 5 +- .../TimeSheetFilterDropdown/index.tsx | 127 ++++++++++++++---- client/src/pages/dtr-management.tsx | 48 ++++--- 6 files changed, 260 insertions(+), 59 deletions(-) diff --git a/api/Schema/Queries/TimeSheetQuery.cs b/api/Schema/Queries/TimeSheetQuery.cs index 9e602349..8aa7cee0 100644 --- a/api/Schema/Queries/TimeSheetQuery.cs +++ b/api/Schema/Queries/TimeSheetQuery.cs @@ -40,9 +40,9 @@ public TimeSheetQuery(TimeSheetService timeSheetService) } [AdminUser] - public async Task> GetTimeEntries(String? date, String? status) + public async Task> GetTimeEntries(String? startDate, String? endDate, String? status) { - return await _timeSheetService.GetAll(date, status); + return await _timeSheetService.GetAll(startDate, endDate, status); } [AdminUser] diff --git a/api/Services/TimeSheetService.cs b/api/Services/TimeSheetService.cs index aeb9551d..4e3b54af 100644 --- a/api/Services/TimeSheetService.cs +++ b/api/Services/TimeSheetService.cs @@ -136,7 +136,7 @@ public static TimeEntryDTO ToTimeEntryDTO(TimeEntry timeEntry, List leave } } - public async Task> GetAll(String? date = null, String? status = null) + public async Task> GetAll(String? startDate = null, String? endDate = null, String? status = null) { var domain = _httpService.getDomainURL(); using (HrisContext context = _contextFactory.CreateDbContext()) @@ -159,16 +159,14 @@ public async Task> GetAll(String? date = null, String? status .Select(entry => ToTimeEntryDTO(entry, leaves, domain, null, null)) .ToListAsync(); - if (date != null) + if (!string.IsNullOrEmpty(startDate) && !string.IsNullOrEmpty(endDate)) { - var filterDate = from entry in entries - where DateOnly.Parse(date).CompareTo(entry.Date) == 0 - select entry; - - entries = filterDate.ToList(); - + var start = DateOnly.Parse(startDate); + var end = DateOnly.Parse(endDate); + entries = entries.Where(entry => entry.Date >= start && entry.Date <= end).ToList(); } + if (status != null) { var filterStatus = from entry in entries diff --git a/client/package-lock.json b/client/package-lock.json index 86fe8b0b..cb62aa25 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -17,6 +17,7 @@ "apexcharts": "^3.36.3", "classnames": "^2.3.2", "cookies-next": "^2.1.1", + "date-fns": "^4.1.0", "emoji-mart": "^5.5.2", "framer-motion": "^10.0.1", "graphql": "^16.6.0", @@ -28,10 +29,11 @@ "next-auth": "^4.18.8", "nextjs-progressbar": "^0.0.16", "rc-tooltip": "^5.2.2", - "react": "18.2.0", + "react": "^18.2.0", "react-apexcharts": "^1.4.0", "react-confirm-alert": "^3.0.6", "react-csv": "^2.2.2", + "react-date-range": "^2.0.1", "react-dom": "18.2.0", "react-feather": "^2.0.10", "react-file-icon": "^1.3.0", @@ -52,6 +54,7 @@ "@types/node": "18.11.3", "@types/react": "18.0.21", "@types/react-csv": "^1.1.10", + "@types/react-date-range": "^1.4.9", "@types/react-dom": "18.0.6", "@types/react-file-icon": "^1.0.1", "@typescript-eslint/eslint-plugin": "^5.47.1", @@ -1271,6 +1274,32 @@ "@types/react": "*" } }, + "node_modules/@types/react-date-range": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@types/react-date-range/-/react-date-range-1.4.9.tgz", + "integrity": "sha512-5oVEDW0ElYmY1+YVSzdMUR8stxSI5QrRJCgCFUvuEAV5197t412vimD9aVTW6g4JTaxCnMmB1BdEOT/odpaBxQ==", + "dev": true, + "dependencies": { + "@types/react": "*", + "date-fns": "^2.16.1" + } + }, + "node_modules/@types/react-date-range/node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/@types/react-dom": { "version": "18.0.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", @@ -2078,6 +2107,15 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -4657,6 +4695,21 @@ "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz", "integrity": "sha512-RG5hOcZKZFigIGE8LxIEV/OgS1vigFQT4EkaHeKgyuCbUAu9Nbd/1RYq++bJcJJ9VOqO/n9TZRADsXNDR4VEpw==" }, + "node_modules/react-date-range": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/react-date-range/-/react-date-range-2.0.1.tgz", + "integrity": "sha512-jwKYc9zcjYMg2hWbPht+6BF2wjGG5DkRVNJLRXn2Y0B/QCOOnvQX6YXziZVujVADWmgsBaoQnILdmzYw+Bwh0g==", + "dependencies": { + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "react-list": "^0.8.13", + "shallow-equal": "^1.2.1" + }, + "peerDependencies": { + "date-fns": "3.0.6 || >=3.0.0", + "react": "^0.14 || ^15.0.0-rc || >=15.0" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -4746,6 +4799,17 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-list": { + "version": "0.8.17", + "resolved": "https://registry.npmjs.org/react-list/-/react-list-0.8.17.tgz", + "integrity": "sha512-pgmzGi0G5uGrdHzMhgO7KR1wx5ZXVvI3SsJUmkblSAKtewIhMwbQiMuQiTE83ozo04BQJbe0r3WIWzSO0dR1xg==", + "dependencies": { + "prop-types": "15" + }, + "peerDependencies": { + "react": "0.14 || 15 - 18" + } + }, "node_modules/react-select": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.0.tgz", @@ -4980,6 +5044,11 @@ "node": ">=10" } }, + "node_modules/shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6526,6 +6595,27 @@ "@types/react": "*" } }, + "@types/react-date-range": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@types/react-date-range/-/react-date-range-1.4.9.tgz", + "integrity": "sha512-5oVEDW0ElYmY1+YVSzdMUR8stxSI5QrRJCgCFUvuEAV5197t412vimD9aVTW6g4JTaxCnMmB1BdEOT/odpaBxQ==", + "dev": true, + "requires": { + "@types/react": "*", + "date-fns": "^2.16.1" + }, + "dependencies": { + "date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.21.0" + } + } + } + }, "@types/react-dom": { "version": "18.0.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.6.tgz", @@ -7088,6 +7178,11 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, + "date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -8892,6 +8987,17 @@ "resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz", "integrity": "sha512-RG5hOcZKZFigIGE8LxIEV/OgS1vigFQT4EkaHeKgyuCbUAu9Nbd/1RYq++bJcJJ9VOqO/n9TZRADsXNDR4VEpw==" }, + "react-date-range": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/react-date-range/-/react-date-range-2.0.1.tgz", + "integrity": "sha512-jwKYc9zcjYMg2hWbPht+6BF2wjGG5DkRVNJLRXn2Y0B/QCOOnvQX6YXziZVujVADWmgsBaoQnILdmzYw+Bwh0g==", + "requires": { + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "react-list": "^0.8.13", + "shallow-equal": "^1.2.1" + } + }, "react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -8950,6 +9056,14 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-list": { + "version": "0.8.17", + "resolved": "https://registry.npmjs.org/react-list/-/react-list-0.8.17.tgz", + "integrity": "sha512-pgmzGi0G5uGrdHzMhgO7KR1wx5ZXVvI3SsJUmkblSAKtewIhMwbQiMuQiTE83ozo04BQJbe0r3WIWzSO0dR1xg==", + "requires": { + "prop-types": "15" + } + }, "react-select": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.0.tgz", @@ -9110,6 +9224,11 @@ "lru-cache": "^6.0.0" } }, + "shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/client/package.json b/client/package.json index 2c3b1f00..30d37aee 100644 --- a/client/package.json +++ b/client/package.json @@ -23,6 +23,7 @@ "apexcharts": "^3.36.3", "classnames": "^2.3.2", "cookies-next": "^2.1.1", + "date-fns": "^4.1.0", "emoji-mart": "^5.5.2", "framer-motion": "^10.0.1", "graphql": "^16.6.0", @@ -34,10 +35,11 @@ "next-auth": "^4.18.8", "nextjs-progressbar": "^0.0.16", "rc-tooltip": "^5.2.2", - "react": "18.2.0", + "react": "^18.2.0", "react-apexcharts": "^1.4.0", "react-confirm-alert": "^3.0.6", "react-csv": "^2.2.2", + "react-date-range": "^2.0.1", "react-dom": "18.2.0", "react-feather": "^2.0.10", "react-file-icon": "^1.3.0", @@ -58,6 +60,7 @@ "@types/node": "18.11.3", "@types/react": "18.0.21", "@types/react-csv": "^1.1.10", + "@types/react-date-range": "^1.4.9", "@types/react-dom": "18.0.6", "@types/react-file-icon": "^1.0.1", "@typescript-eslint/eslint-plugin": "^5.47.1", diff --git a/client/src/components/molecules/TimeSheetFilterDropdown/index.tsx b/client/src/components/molecules/TimeSheetFilterDropdown/index.tsx index a01a1b5d..81b87297 100644 --- a/client/src/components/molecules/TimeSheetFilterDropdown/index.tsx +++ b/client/src/components/molecules/TimeSheetFilterDropdown/index.tsx @@ -1,41 +1,87 @@ import moment from 'moment' -import React, { FC } from 'react' +import React, { FC, useState } from 'react' import classNames from 'classnames' import Text from '~/components/atoms/Text' import { Filters } from '~/pages/dtr-management' import Button from '~/components/atoms/Buttons/ButtonAction' import FilterDropdownTemplate from '~/components/templates/FilterDropdownTemplate' +import { DateRange, Range } from 'react-date-range' +import 'react-date-range/dist/styles.css' +import 'react-date-range/dist/theme/default.css' type Props = { filters: Filters setFilters: React.Dispatch> - handleFilterUpdate: Function + handleFilterUpdate: (startDate: Date, endDate: Date) => void isOpenSummaryTable: boolean } const TimeSheetFilterDropdown: FC = (props): JSX.Element => { const { filters, setFilters, handleFilterUpdate, isOpenSummaryTable } = props - const dateSelectionRef = React.createRef() - const monthYearSelectionRef = React.createRef() const daysRangeSelectionRef = React.createRef() const statusOptions = ['All', 'Present', 'Sick Leave', 'Vacation Leave', 'Absent'] - const daysRangeOptions = ['1-15 Days Timesheet', '16-31 Days Timesheet'] + const daysRangeOptions = ['1-15 Days Timesheet', '16-31 Days Timesheet', '1-31 Days Timesheet'] - const filterStatusOptions = (statusList: string[]): JSX.Element[] => { - return statusList.map((item) => ) + const [state, setState] = useState([ + { + startDate: new Date(), + endDate: undefined, + key: 'selection' + } + ]) + + const handleButtonClick = (rangeType: string): void => { + const monthStart = moment().startOf('month').toDate() + let startDate, endDate + + if (rangeType === '1-15') { + startDate = monthStart + endDate = moment(monthStart).add(14, 'days').toDate() + } else if (rangeType === '16-31') { + startDate = moment(monthStart).add(15, 'days').toDate() + endDate = moment(monthStart).endOf('month').toDate() + } else if (rangeType === '1-31') { + startDate = monthStart + endDate = moment(monthStart).endOf('month').toDate() + } + + // Update calendar range + setState([{ startDate, endDate, key: 'selection' }]) + + // Also update filters + setFilters({ + ...filters, + startDate: moment(startDate).format('YYYY-MM-DD'), + endDate: moment(endDate).format('YYYY-MM-DD') + }) } - const handleDateChange = (): void => { - const selectedDate = - dateSelectionRef.current != null ? new Date(dateSelectionRef.current.value) : new Date() + const handleUpdateResults = (): void => { + const { startDate, endDate } = state[0] + + if ( + startDate !== null && + startDate !== undefined && + endDate !== null && + endDate !== undefined + ) { + setFilters({ + ...filters, + startDate: moment(startDate).format('YYYY-MM-DD'), + endDate: moment(endDate).format('YYYY-MM-DD') + }) + handleFilterUpdate(startDate, endDate) + } + } - setFilters({ ...filters, date: moment(selectedDate).format('YYYY-MM-DD') }) + const filterStatusOptions = (statusList: string[]): JSX.Element[] => { + return statusList.map((item) => ) } const handleStatusChange = (e: React.ChangeEvent): void => { @@ -72,7 +118,7 @@ const TimeSheetFilterDropdown: FC = (props): JSX.Element => { } const menuItems = classNames( - 'w-80 rounded-md ring-opacity-5 focus:outline-none top-8 right-0', + 'w-90 rounded-md ring-opacity-5 focus:outline-none top-8 right-0', 'bg-white py-1 shadow-xl shadow-slate-200 ring-1 ring-black' ) @@ -111,18 +157,13 @@ const TimeSheetFilterDropdown: FC = (props): JSX.Element => { ) : ( <> -
- -
+ setState([item.selection])} + moveRangeOnFirstSelection={false} + ranges={state} + /> + + +
+ + + +
)} @@ -146,7 +223,7 @@ const TimeSheetFilterDropdown: FC = (props): JSX.Element => { variant="primary" rounded="md" className="w-full py-2 text-xs" - onClick={(): React.MouseEvent => handleFilterUpdate()} + onClick={handleUpdateResults} > Update Results diff --git a/client/src/pages/dtr-management.tsx b/client/src/pages/dtr-management.tsx index 613e6bf3..e7c74c58 100644 --- a/client/src/pages/dtr-management.tsx +++ b/client/src/pages/dtr-management.tsx @@ -81,12 +81,8 @@ const DTRManagement: NextPage = (): JSX.Element => { const [filters, setFilters] = useState({ date: moment().format('YYYY-MM-DD'), status: '', - startDate: - new Date().getDate() > 15 ? moment().format('YYYY-MM-16') : moment().format('YYYY-MM-01'), - endDate: - new Date().getDate() > 15 - ? moment().endOf('month').format('YYYY-MM-DD') - : moment().format('YYYY-MM-15') + startDate: moment().startOf('month').format('YYYY-MM-DD'), + endDate: moment().endOf('month').format('YYYY-MM-DD') }) const queryVariables: QueryVariablesType = { @@ -97,8 +93,8 @@ const DTRManagement: NextPage = (): JSX.Element => { } const allEmployee = getAllEmployeeTimesheet( - '$date: String, $status: String', - 'date: $date, status: $status', + '$startDate: String, $endDate:String, $status: String', + 'startDate: $startDate, endDate: $endDate, status: $status', queryVariables, fetchReady ) @@ -145,6 +141,7 @@ const DTRManagement: NextPage = (): JSX.Element => { isLoading: response.isLoading }) }) + // DTR-Management Page } else { handleURLParameterChange({ date: filters.date, @@ -162,6 +159,22 @@ const DTRManagement: NextPage = (): JSX.Element => { }) } } + const startDate = filters.startDate + const endDate = filters.endDate + + const formatDateForFilename = (date: { toISOString: () => string }): string => { + // Format the date as desired, e.g., "YYYY-MM-DD" + return date.toISOString().split('T')[0] + } + + const getFilenameWithDateRange = (): string => { + const formattedStartDate = formatDateForFilename(new Date(startDate)) + const formattedEndDate = formatDateForFilename(new Date(endDate)) + + return isOpenSummaryTable + ? `Summary_${formattedStartDate}_to_${formattedEndDate}.csv` + : `DTR_${formattedStartDate}_to_${formattedEndDate}.csv` + } const { useAllWorkInterruptions } = useInterruptionType() const workInterruption = useAllWorkInterruptions() @@ -190,17 +203,6 @@ const DTRManagement: NextPage = (): JSX.Element => { }) } } - const getSummaryFilename = (): string => { - const startDate = moment(filters.startDate) - const endDate = moment(filters.endDate) - const dateRange = - startDate.date() === 1 && endDate.date() === 15 - ? '1-15' - : startDate.date() === 16 - ? '16-31' - : `${startDate.date()}-${endDate.date()}` - return `Summary-${startDate.format('MMM-YYYY')}-(${dateRange}).csv` - } useEffect(() => { if (router.isReady) { @@ -366,7 +368,7 @@ const DTRManagement: NextPage = (): JSX.Element => { : fetchedAllEmployeeData.data?.timeEntries ?? [] } headers={isOpenSummaryTable ? SummaryHeaders : DTRheaders} - filename={isOpenSummaryTable ? getSummaryFilename() : `DTR_${filters.date}`} + filename={getFilenameWithDateRange()} > + + + ) : ( <>