diff --git a/apps/web/public/icons/sprite.svg b/apps/web/public/icons/sprite.svg index 36cf696c71..87b4c19153 100644 --- a/apps/web/public/icons/sprite.svg +++ b/apps/web/public/icons/sprite.svg @@ -118,6 +118,16 @@ + + + + + + + + + + @@ -548,6 +558,17 @@ + + + + + + + + + + + diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index e23a605b6e..217f88e9d6 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -2078,6 +2078,7 @@ "looking_for_more_analytics": "Looking for more analytics?", "looking_for_more_insights": "Looking for more Insights?", "filters": "Filters", + "filter": "Filter", "add_filter": "Add filter", "add_rule": "Add rule", "add_rule_group": "Add rule group", @@ -2741,10 +2742,10 @@ "no_matching_members_will_fallback_to_all_assigned_members": "No matching members. It will fallback to using the team members assigned to the event type. Consider adding a fallback or correcting the logic of using_fallback_members", "hide_calendar_event_details": "Hide calendar event details on shared calendars", "description_hide_calendar_event_details": "When a calendar is shared, events are visible to readers but their details are hidden from those without write access.", - "last_number_of_days": "last {{count}} days", - "month_to_date": "month to date", - "year_to_date": "year to date", - "custom_range": "custom range", + "last_number_of_days": "Last {{count}} days", + "month_to_date": "Month to date", + "year_to_date": "Year to date", + "custom_range": "Custom range", "refund_policy": "Refund Policy", "always": "Always", "never": "Never", diff --git a/packages/features/data-table/components/DataTableWrapper.tsx b/packages/features/data-table/components/DataTableWrapper.tsx index 4306bab17e..8419c40b14 100644 --- a/packages/features/data-table/components/DataTableWrapper.tsx +++ b/packages/features/data-table/components/DataTableWrapper.tsx @@ -70,8 +70,8 @@ export function DataTableWrapper({
-
{ToolbarLeft}
-
{ToolbarRight}
+
{ToolbarLeft}
+
{ToolbarRight}
diff --git a/packages/features/data-table/components/filters/ClearFiltersButton.tsx b/packages/features/data-table/components/filters/ClearFiltersButton.tsx new file mode 100644 index 0000000000..341f3a632c --- /dev/null +++ b/packages/features/data-table/components/filters/ClearFiltersButton.tsx @@ -0,0 +1,27 @@ +import { useDataTable } from "@calcom/features/data-table"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { Icon, Button, Tooltip } from "@calcom/ui"; + +export const ClearFiltersButton = ({ exclude }: { exclude?: string[] }) => { + const { t } = useLocale(); + const { activeFilters, clearAll } = useDataTable(); + + if (!activeFilters?.length || (exclude && activeFilters.every((filter) => exclude.includes(filter.f)))) { + return null; + } + + return ( + + + + ); +}; diff --git a/packages/features/data-table/components/filters/DateRangeFilter.tsx b/packages/features/data-table/components/filters/DateRangeFilter.tsx new file mode 100644 index 0000000000..7d9c08e675 --- /dev/null +++ b/packages/features/data-table/components/filters/DateRangeFilter.tsx @@ -0,0 +1,191 @@ +import type { Dayjs } from "dayjs"; +import { useState } from "react"; + +import dayjs from "@calcom/dayjs"; +import { classNames } from "@calcom/lib"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { DateRangePicker } from "@calcom/ui"; +import { Select } from "@calcom/ui"; + +import "../../../insights/filters/DateSelect.css"; +import { useDataTable, useFilterValue } from "../../hooks"; +import type { FilterableColumn } from "../../lib/types"; +import { ZDateRangeFilterValue } from "../../lib/types"; + +type PresetOption = { + labelKey: string; + i18nOptions?: Record; + value: string; +}; + +type DateRangeFilterProps = { + className?: string; + column: Extract; +}; + +const CUSTOM_PRESET_VALUE = "c" as const; + +const DEFAULT_PRESET: PresetOption = { + labelKey: "last_number_of_days", + i18nOptions: { count: 7 }, + value: "w", +}; +const CUSTOM_PRESET: PresetOption = { labelKey: "custom_range", value: CUSTOM_PRESET_VALUE }; + +const PRESET_OPTIONS: PresetOption[] = [ + { labelKey: "today", value: "tdy" }, + DEFAULT_PRESET, + { labelKey: "last_number_of_days", i18nOptions: { count: 30 }, value: "t" }, + { labelKey: "month_to_date", value: "m" }, + { labelKey: "year_to_date", value: "y" }, + CUSTOM_PRESET, +]; + +const getDateRangeFromPreset = (val: string | null) => { + let startDate; + let endDate; + const preset = PRESET_OPTIONS.find((o) => o.value === val); + if (!preset) { + return { startDate: getDefaultStartDate(), endDate: getDefaultEndDate(), preset: CUSTOM_PRESET }; + } + + switch (val) { + case "tdy": // Today + startDate = dayjs().startOf("day"); + endDate = dayjs().endOf("day"); + break; + case "w": // Last 7 days + startDate = dayjs().subtract(1, "week").startOf("day"); + endDate = dayjs().endOf("day"); + break; + case "t": // Last 30 days + startDate = dayjs().subtract(30, "day").startOf("day"); + endDate = dayjs().endOf("day"); + break; + case "m": // Month to Date + startDate = dayjs().startOf("month"); + endDate = dayjs().endOf("day"); + break; + case "y": // Year to Date + startDate = dayjs().startOf("year"); + endDate = dayjs().endOf("day"); + break; + default: + startDate = getDefaultStartDate(); + endDate = getDefaultEndDate(); + break; + } + + return { startDate, endDate, preset }; +}; + +const getDefaultStartDate = () => dayjs().subtract(1, "week").startOf("day"); + +const getDefaultEndDate = () => dayjs().endOf("day"); + +export const DateRangeFilter = ({ className, column }: DateRangeFilterProps) => { + const filterValue = useFilterValue(column.id, ZDateRangeFilterValue); + const { updateFilter } = useDataTable(); + + const { t } = useLocale(); + const currentDate = dayjs(); + const [startDate, setStartDate] = useState( + filterValue?.data.startDate ? dayjs(filterValue.data.startDate) : getDefaultStartDate() + ); + const [endDate, setEndDate] = useState( + filterValue?.data.endDate ? dayjs(filterValue.data.endDate) : getDefaultEndDate() + ); + const [selectedPreset, setSelectedPreset] = useState(DEFAULT_PRESET); + + const updateValues = ({ + preset, + startDate, + endDate, + }: { + preset: PresetOption; + startDate: Dayjs; + endDate?: Dayjs; + }) => { + setSelectedPreset(preset); + setStartDate(startDate); + setEndDate(endDate); + + if (startDate && endDate) { + updateFilter(column.id, { + type: "date_range", + data: { + startDate: startDate.toDate().toISOString(), + endDate: endDate.toDate().toISOString(), + preset: preset.value, + }, + }); + } + }; + + const updateDateRangeFromPreset = (val: string | null) => { + if (val === CUSTOM_PRESET_VALUE) { + updateValues({ + preset: CUSTOM_PRESET, + startDate, + endDate, + }); + } else { + const r = getDateRangeFromPreset(val); + updateValues({ + preset: r.preset, + startDate: r.startDate, + endDate: r.endDate, + }); + } + }; + + const updateDateRangeFromPicker = ({ + startDate, + endDate, + }: { + startDate?: Date | undefined; + endDate?: Date | undefined; + }) => { + updateValues({ + preset: CUSTOM_PRESET, + startDate: startDate ? dayjs(startDate) : getDefaultStartDate(), + endDate: endDate ? dayjs(endDate) : undefined, + }); + }; + + return ( +
+ +