fix: insights UI/UX (#16610)
* remove old code remnant * - * WIP * type change * -- * typefix * fix type * fix type * change type name * add basewhere to eventsByStatus * add basewhere to the rest * fix rendering null popular event list item * URL params for start and endtime to have YYYY-MM-DD format * remove console log * resolve suggestion --------- Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
This commit is contained in:
co-authored by
Udit Takkar
parent
2872b263fd
commit
38fa0fae1d
@@ -2618,5 +2618,9 @@
|
||||
"you_are_unauthorized_to_make_this_change_to_the_booking": "You are unauthorized to make this change to the booking",
|
||||
"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",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
|
||||
@@ -14,13 +14,14 @@ export const AverageEventDurationChart = () => {
|
||||
const { filter } = useFilterContext();
|
||||
const { dateRange, selectedMemberUserId, isAll, initialConfig } = filter;
|
||||
const [startDate, endDate] = dateRange;
|
||||
const { selectedTeamId: teamId, selectedUserId } = filter;
|
||||
const { selectedTeamId: teamId, selectedUserId, selectedEventTypeId } = filter;
|
||||
const initialConfigIsReady = !!(initialConfig?.teamId || initialConfig?.userId || initialConfig?.isAll);
|
||||
const { data, isSuccess, isPending } = trpc.viewer.insights.averageEventDuration.useQuery(
|
||||
{
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
teamId: teamId ?? undefined,
|
||||
teamId,
|
||||
eventTypeId: selectedEventTypeId ?? undefined,
|
||||
memberUserId: selectedMemberUserId ?? undefined,
|
||||
userId: selectedUserId ?? undefined,
|
||||
isAll,
|
||||
|
||||
@@ -11,6 +11,7 @@ import { KPICard } from "./KPICard";
|
||||
export const BookingKPICards = () => {
|
||||
const { t } = useLocale();
|
||||
const { filter } = useFilterContext();
|
||||
|
||||
const { dateRange, selectedEventTypeId, selectedUserId, selectedMemberUserId, isAll, initialConfig } =
|
||||
filter;
|
||||
const initialConfigIsReady = !!(initialConfig?.teamId || initialConfig?.userId || initialConfig?.isAll);
|
||||
|
||||
@@ -10,7 +10,8 @@ import { LoadingInsight } from "./LoadingInsights";
|
||||
export const PopularEventsTable = () => {
|
||||
const { t } = useLocale();
|
||||
const { filter } = useFilterContext();
|
||||
const { dateRange, selectedMemberUserId, selectedUserId, isAll, initialConfig } = filter;
|
||||
const { dateRange, selectedMemberUserId, selectedUserId, isAll, initialConfig, selectedEventTypeId } =
|
||||
filter;
|
||||
const [startDate, endDate] = dateRange;
|
||||
const { selectedTeamId: teamId } = filter;
|
||||
|
||||
@@ -20,6 +21,7 @@ export const PopularEventsTable = () => {
|
||||
endDate: endDate.toISOString(),
|
||||
teamId: teamId ?? undefined,
|
||||
userId: selectedUserId ?? undefined,
|
||||
eventTypeId: selectedEventTypeId ?? undefined,
|
||||
memberUserId: selectedMemberUserId ?? undefined,
|
||||
isAll,
|
||||
},
|
||||
@@ -41,16 +43,19 @@ export const PopularEventsTable = () => {
|
||||
<Title className="text-emphasis">{t("popular_events")}</Title>
|
||||
<Table className="mt-5">
|
||||
<TableBody>
|
||||
{data.map((item) => (
|
||||
<TableRow key={item.eventTypeId}>
|
||||
<TableCell className="text-default">{item.eventTypeName}</TableCell>
|
||||
<TableCell>
|
||||
<Text className="text-default text-right">
|
||||
<strong>{item.count}</strong>
|
||||
</Text>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{data.map(
|
||||
(item) =>
|
||||
item.eventTypeId && (
|
||||
<TableRow key={item.eventTypeId}>
|
||||
<TableCell className="text-default">{item.eventTypeName}</TableCell>
|
||||
<TableCell>
|
||||
<Text className="text-default text-right">
|
||||
<strong>{item.count}</strong>
|
||||
</Text>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{data.length === 0 && (
|
||||
|
||||
@@ -60,7 +60,7 @@ export function FiltersProvider({ children }: { children: React.ReactNode }) {
|
||||
dateRange: [
|
||||
startTimeParsed ? dayjs(startTimeParsed) : dayjs().subtract(1, "week"),
|
||||
endTimeParsed ? dayjs(endTimeParsed) : dayjs(),
|
||||
"w",
|
||||
!startTimeParsed && !endTimeParsed ? "w" : null,
|
||||
],
|
||||
selectedTimeView: "week",
|
||||
selectedUserId: userIdParsed || null,
|
||||
@@ -131,8 +131,8 @@ export function FiltersProvider({ children }: { children: React.ReactNode }) {
|
||||
setParamsIfDefined("userId", selectedUserId || initialConfig?.userId);
|
||||
setParamsIfDefined("eventTypeId", selectedEventTypeId);
|
||||
setParamsIfDefined("isAll", isAll || initialConfig?.isAll);
|
||||
setParamsIfDefined("startTime", startTime?.toISOString());
|
||||
setParamsIfDefined("endTime", endTime?.toISOString());
|
||||
setParamsIfDefined("startTime", startTime?.format("YYYY-MM-DD"));
|
||||
setParamsIfDefined("endTime", endTime?.format("YYYY-MM-DD"));
|
||||
setParamsIfDefined("filter", selectedFilter?.[0]);
|
||||
router.push(`${pathname}?${newSearchParams.toString()}`);
|
||||
},
|
||||
|
||||
@@ -1,68 +1,102 @@
|
||||
import { DateRangePicker } from "@tremor/react";
|
||||
import { useState } from "react";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { DateRangePicker } from "@calcom/ui";
|
||||
import { Select } from "@calcom/ui";
|
||||
|
||||
import { useFilterContext } from "../context/provider";
|
||||
import "./DateSelect.css";
|
||||
|
||||
type RangeType = "tdy" | "w" | "t" | "m" | "y" | undefined | null;
|
||||
|
||||
export const DateSelect = () => {
|
||||
const { t } = useLocale();
|
||||
const presetOptions = [
|
||||
{ label: t("today"), value: "tdy" },
|
||||
{ label: t("last_number_of_days", { count: 7 }), value: "w" },
|
||||
{ label: t("last_number_of_days", { count: 30 }), value: "t" },
|
||||
{ label: t("month_to_date"), value: "m" },
|
||||
{ label: t("year_to_date"), value: "y" },
|
||||
{ label: t("custom_range"), value: null },
|
||||
];
|
||||
|
||||
const { filter, setConfigFilters } = useFilterContext();
|
||||
const currentDate = dayjs();
|
||||
const [startDate, endDate, range] = filter?.dateRange || [null, null, null];
|
||||
const [initialStartDate, initialEndDate, range] = filter?.dateRange || [null, null, null];
|
||||
const [startDate, setStartDate] = useState(initialStartDate);
|
||||
const [endDate, setEndDate] = useState(initialEndDate);
|
||||
const startValue = startDate?.toDate() || null;
|
||||
const endValue = endDate?.toDate() || null;
|
||||
const [selectedPreset, setSelectedPreset] = useState(presetOptions.find((c) => c.value === range));
|
||||
|
||||
const updateDateRange = (val: string | null) => {
|
||||
setSelectedPreset(presetOptions.find((c) => c.value === val));
|
||||
let startDate = dayjs();
|
||||
let endDate = dayjs();
|
||||
|
||||
switch (val) {
|
||||
case "tdy": // Today
|
||||
startDate = dayjs().startOf("day");
|
||||
endDate = dayjs().endOf("day");
|
||||
break;
|
||||
case "w": // Last 7 days
|
||||
startDate = dayjs().subtract(7, "day").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:
|
||||
break;
|
||||
}
|
||||
// Update the datepicker date selection
|
||||
setStartDate(startDate);
|
||||
setEndDate(endDate);
|
||||
// Update the configuration filter
|
||||
setConfigFilters({
|
||||
dateRange: [dayjs(startDate), dayjs(endDate), val],
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="custom-date max-w-96 w-full sm:w-auto">
|
||||
<div className="ml me-2 ms-2 inline-flex space-x-2 rtl:space-x-reverse">
|
||||
<DateRangePicker
|
||||
value={[startValue, endValue, range]}
|
||||
defaultValue={[startValue, endValue, range]}
|
||||
onValueChange={(datesArray) => {
|
||||
const [selected, ...rest] = datesArray;
|
||||
const [start, end, range] = datesArray;
|
||||
// If range has value and it's of type RangeType
|
||||
|
||||
if (
|
||||
range &&
|
||||
(range === "tdy" || range === "w" || range === "t" || range === "m" || range === "y")
|
||||
) {
|
||||
setConfigFilters({
|
||||
dateRange: [dayjs(start).startOf("d"), dayjs(end).endOf("d"), range],
|
||||
});
|
||||
|
||||
return;
|
||||
} else if (start && !end) {
|
||||
// If only start time has value that means selected date should push to dateRange with last value null
|
||||
const currentDates = filter.dateRange;
|
||||
if (currentDates && currentDates.length > 0) {
|
||||
// remove last position of array
|
||||
currentDates.pop();
|
||||
// push new value to array
|
||||
currentDates.push(dayjs(selected));
|
||||
// if lenght > 2 then remove first value
|
||||
if (currentDates.length > 2) {
|
||||
currentDates.shift();
|
||||
}
|
||||
setConfigFilters({
|
||||
dateRange: [currentDates[0], currentDates[1], null],
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If range has value and it's of type RangeType
|
||||
dates={{
|
||||
startDate: startValue,
|
||||
endDate: endValue,
|
||||
}}
|
||||
options={undefined}
|
||||
enableDropdown={true}
|
||||
placeholder={t("select_date_range")}
|
||||
enableYearPagination={true}
|
||||
minDate={currentDate.subtract(2, "year").toDate()}
|
||||
maxDate={currentDate.toDate()}
|
||||
color="gray"
|
||||
disabled={false}
|
||||
onDatesChange={({ startDate, endDate }) => {
|
||||
setConfigFilters({
|
||||
dateRange: [dayjs(startDate), dayjs(endDate), null],
|
||||
});
|
||||
setStartDate(dayjs(startDate));
|
||||
setEndDate(dayjs(endDate));
|
||||
setSelectedPreset(presetOptions.find((c) => c.value === null));
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
variant="default"
|
||||
data-testid="insights-preset"
|
||||
options={presetOptions}
|
||||
value={selectedPreset}
|
||||
className="w-40 capitalize text-black"
|
||||
defaultValue={selectedPreset}
|
||||
onChange={(e) => {
|
||||
if (e) {
|
||||
updateDateRange(e.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -72,7 +72,7 @@ export const Filters = () => {
|
||||
/>
|
||||
</Tooltip>
|
||||
</ButtonGroup> */}
|
||||
<div className="flex flex-col-reverse gap-2 sm:flex-row sm:flex-nowrap sm:justify-between">
|
||||
<div className="flex flex-col-reverse sm:flex-row sm:flex-nowrap sm:justify-between">
|
||||
<Download />
|
||||
<DateSelect />
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,10 +11,18 @@ import { buttonClasses } from "../../button/Button";
|
||||
|
||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
|
||||
|
||||
function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
|
||||
function Calendar({
|
||||
className,
|
||||
classNames,
|
||||
fromDate,
|
||||
toDate,
|
||||
showOutsideDays = true,
|
||||
...props
|
||||
}: CalendarProps) {
|
||||
return (
|
||||
<DayPicker
|
||||
fromDate={new Date()}
|
||||
fromDate={fromDate || new Date()}
|
||||
toDate={toDate || undefined}
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn("p-3", className)}
|
||||
classNames={{
|
||||
|
||||
@@ -14,11 +14,15 @@ type DatePickerWithRangeProps = {
|
||||
dates: { startDate: Date; endDate?: Date };
|
||||
onDatesChange: ({ startDate, endDate }: { startDate?: Date; endDate?: Date }) => void;
|
||||
disabled?: boolean;
|
||||
minDate?: Date;
|
||||
maxDate?: Date;
|
||||
};
|
||||
|
||||
export function DatePickerWithRange({
|
||||
className,
|
||||
dates,
|
||||
minDate,
|
||||
maxDate,
|
||||
onDatesChange,
|
||||
disabled,
|
||||
}: React.HTMLAttributes<HTMLDivElement> & DatePickerWithRangeProps) {
|
||||
@@ -56,6 +60,8 @@ export function DatePickerWithRange({
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
initialFocus
|
||||
fromDate={minDate}
|
||||
toDate={maxDate}
|
||||
mode="range"
|
||||
defaultMonth={dates?.startDate}
|
||||
selected={{ from: dates?.startDate, to: dates?.endDate }}
|
||||
|
||||
Reference in New Issue
Block a user