diff --git a/worklenz-frontend/public/locales/en/time-report.json b/worklenz-frontend/public/locales/en/time-report.json index 5d73c247..4a418b6d 100644 --- a/worklenz-frontend/public/locales/en/time-report.json +++ b/worklenz-frontend/public/locales/en/time-report.json @@ -5,6 +5,7 @@ "searchByName": "Search by name", "selectAll": "Select All", + "clearAll": "Clear All", "teams": "Teams", "searchByProject": "Search by project name", @@ -15,6 +16,8 @@ "billable": "Billable", "nonBillable": "Non Billable", + "filterByBillableStatus": "Filter by Billable Status", + "allBillableTypes": "All Billable Types", "total": "Total", diff --git a/worklenz-frontend/public/locales/es/time-report.json b/worklenz-frontend/public/locales/es/time-report.json index 03fdd744..eb99c8e5 100644 --- a/worklenz-frontend/public/locales/es/time-report.json +++ b/worklenz-frontend/public/locales/es/time-report.json @@ -5,6 +5,7 @@ "searchByName": "Buscar por nombre", "selectAll": "Seleccionar Todo", + "clearAll": "Limpiar Todo", "teams": "Equipos", "searchByProject": "Buscar por nombre de proyecto", @@ -15,6 +16,8 @@ "billable": "Facturable", "nonBillable": "No Facturable", + "filterByBillableStatus": "Filtrar por Estado Facturable", + "allBillableTypes": "Todos los Tipos Facturables", "total": "Total", diff --git a/worklenz-frontend/public/locales/pt/time-report.json b/worklenz-frontend/public/locales/pt/time-report.json index 97679df8..2467274d 100644 --- a/worklenz-frontend/public/locales/pt/time-report.json +++ b/worklenz-frontend/public/locales/pt/time-report.json @@ -5,6 +5,7 @@ "searchByName": "Pesquisar por nome", "selectAll": "Selecionar Todos", + "clearAll": "Limpar Todos", "teams": "Equipes", "searchByProject": "Pesquisar por nome do projeto", @@ -15,6 +16,8 @@ "billable": "Cobrável", "nonBillable": "Não Cobrável", + "filterByBillableStatus": "Filtrar por Status de Cobrança", + "allBillableTypes": "Todos os Tipos Cobráveis", "total": "Total", diff --git a/worklenz-frontend/src/pages/reporting/timeReports/page-header/billable.tsx b/worklenz-frontend/src/pages/reporting/timeReports/page-header/billable.tsx index ffb15f59..cea08c8e 100644 --- a/worklenz-frontend/src/pages/reporting/timeReports/page-header/billable.tsx +++ b/worklenz-frontend/src/pages/reporting/timeReports/page-header/billable.tsx @@ -1,45 +1,236 @@ import { setSelectOrDeselectBillable } from '@/features/reporting/time-reports/time-reports-overview.slice'; import { useAppDispatch } from '@/hooks/useAppDispatch'; import { useAppSelector } from '@/hooks/useAppSelector'; -import { CaretDownFilled } from '@ant-design/icons'; -import { Button, Checkbox, Dropdown, MenuProps } from 'antd'; -import React from 'react'; +import { CaretDownFilled, FilterOutlined, CheckCircleFilled } from '@ant-design/icons'; +import { Button, Checkbox, Dropdown, MenuProps, Space, Badge, Divider, theme } from 'antd'; +import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; const Billable: React.FC = () => { const { t } = useTranslation('time-report'); const dispatch = useAppDispatch(); + const { token } = theme.useToken(); const { billable } = useAppSelector(state => state.timeReportsOverviewReducer); + // Calculate active filters count + const activeFiltersCount = useMemo(() => { + let count = 0; + if (billable.billable) count++; + if (billable.nonBillable) count++; + return count; + }, [billable.billable, billable.nonBillable]); + + // Check if all options are selected + const isAllSelected = billable.billable && billable.nonBillable; + const isNoneSelected = !billable.billable && !billable.nonBillable; + + // Handle select all + const handleSelectAll = () => { + dispatch(setSelectOrDeselectBillable({ + billable: true, + nonBillable: true + })); + }; + + // Handle clear all + const handleClearAll = () => { + dispatch(setSelectOrDeselectBillable({ + billable: false, + nonBillable: false + })); + }; + + // Theme-aware colors + const isDark = token.colorBgContainer !== '#ffffff'; + const colors = { + headerText: isDark ? token.colorTextSecondary : '#262626', + borderColor: isDark ? token.colorBorder : '#f0f0f0', + linkActive: token.colorPrimary, + linkDisabled: isDark ? token.colorTextDisabled : '#d9d9d9', + successColor: token.colorSuccess, + errorColor: token.colorError, + buttonBorder: activeFiltersCount > 0 ? token.colorPrimary : token.colorBorder, + buttonText: activeFiltersCount > 0 ? token.colorPrimary : token.colorTextSecondary, + buttonBg: activeFiltersCount > 0 ? (isDark ? token.colorPrimaryBg : '#f6ffed') : 'transparent', + dropdownBg: token.colorBgElevated, + dropdownBorder: token.colorBorderSecondary, + }; + // Dropdown items for the menu const menuItems: MenuProps['items'] = [ { - key: 'search', - label: {t('billable')}, + key: 'header', + label: ( +
+ {t('filterByBillableStatus')} +
+ ), + disabled: true, + }, + { + key: 'actions', + label: ( +
+ + + + + +
+ ), + disabled: true, + }, + { + key: 'billable', + label: ( +
+ + {t('billable')} + + {billable.billable && ( + + )} +
+ ), onClick: () => { dispatch(setSelectOrDeselectBillable({ ...billable, billable: !billable.billable })); }, }, { - key: 'selectAll', - label: {t('nonBillable')}, + key: 'nonBillable', + label: ( +
+ + {t('nonBillable')} + + {billable.nonBillable && ( + + )} +
+ ), onClick: () => { dispatch(setSelectOrDeselectBillable({ ...billable, nonBillable: !billable.nonBillable })); }, }, ]; + // Button text based on selection state + const getButtonText = () => { + if (isNoneSelected) return t('billable'); + if (isAllSelected) return t('allBillableTypes'); + if (billable.billable && !billable.nonBillable) return t('billable'); + if (!billable.billable && billable.nonBillable) return t('nonBillable'); + return t('billable'); + }; + return (
-
diff --git a/worklenz-frontend/src/pages/reporting/timeReports/page-header/time-report-page-header.tsx b/worklenz-frontend/src/pages/reporting/timeReports/page-header/time-report-page-header.tsx index 4b7ab36d..de2a9cf1 100644 --- a/worklenz-frontend/src/pages/reporting/timeReports/page-header/time-report-page-header.tsx +++ b/worklenz-frontend/src/pages/reporting/timeReports/page-header/time-report-page-header.tsx @@ -1,4 +1,5 @@ import React, { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; import Team from './team'; import Categories from './categories'; import Projects from './projects'; @@ -16,18 +17,26 @@ import Utilization from './utilization'; const TimeReportPageHeader: React.FC = () => { const dispatch = useAppDispatch(); + const location = useLocation(); + + // Check if current route is members time sheet + const isMembersTimeSheet = location.pathname.includes('time-sheet-members'); useEffect(() => { const fetchData = async () => { await dispatch(fetchReportingTeams()); await dispatch(fetchReportingCategories()); await dispatch(fetchReportingProjects()); - await dispatch(fetchReportingMembers()); - await dispatch(fetchReportingUtilization()); + + // Only fetch members and utilization data for members time sheet + if (isMembersTimeSheet) { + await dispatch(fetchReportingMembers()); + await dispatch(fetchReportingUtilization()); + } }; fetchData(); - }, [dispatch]); + }, [dispatch, isMembersTimeSheet]); return (
@@ -35,8 +44,8 @@ const TimeReportPageHeader: React.FC = () => { - - + {isMembersTimeSheet && } + {isMembersTimeSheet && }
); };