Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

/insights #6511

Merged
merged 60 commits into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from 59 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
09143be
init page
PeerRich Jan 16, 2023
a177a8e
init insights frontend
PeerRich Jan 16, 2023
016b6fe
nit
PeerRich Jan 16, 2023
49635a4
nit
PeerRich Jan 16, 2023
53987c9
nit
PeerRich Jan 19, 2023
717cdba
merge
PeerRich Feb 11, 2023
79b9bb7
merge
PeerRich Feb 11, 2023
1aa07a0
fixed icons
PeerRich Feb 12, 2023
ee4078d
i18n, needs features
PeerRich Feb 12, 2023
8ffc7e1
Merge branch 'main' into 6507-cal-803-analytics
PeerRich Feb 12, 2023
1dffc8e
Merge branch 'main' of ssh://github.com/calcom/cal.com into 6507-cal-…
alannnc Feb 15, 2023
efcda16
Init insights trpc
alannnc Feb 21, 2023
6341fd1
Using trpc on client
alannnc Feb 21, 2023
2fe5c31
Merge with main
alannnc Feb 27, 2023
c72382f
Added events timeline
alannnc Feb 27, 2023
1f63304
Seed analytics script
alannnc Feb 27, 2023
dd95b3e
connect ui with trpc
alannnc Feb 27, 2023
6745f0e
Added and fixed event time lines
alannnc Feb 28, 2023
a5d983f
merge with main
alannnc Feb 28, 2023
6a46537
WIP popular days and avg time duration event type
alannnc Mar 1, 2023
f6fe6c2
Merge with main
alannnc Mar 1, 2023
983d855
Merge branch 'main' of ssh://github.com/calcom/cal.com into 6507-cal-…
alannnc Mar 2, 2023
2a11c12
added new metric graphs
alannnc Mar 3, 2023
2e29225
improved upgrade tip
PeerRich Mar 13, 2023
84dc305
always show upgrade screen
PeerRich Mar 13, 2023
4328cc5
Merge with main
alannnc Mar 14, 2023
a8294e5
Merge branch 'main' of ssh://github.com/calcom/cal.com into 6507-cal-…
alannnc Mar 15, 2023
e9832ef
upgrade tremor.so and select inputs for page
alannnc Mar 16, 2023
962896c
Remove log
alannnc Mar 16, 2023
6b77b6c
merge with main
alannnc Mar 16, 2023
6df9bdc
Merge branch 'main' into 6507-cal-803-analytics
PeerRich Mar 16, 2023
da8dd60
Merge branch 'main' into 6507-cal-803-analytics
PeerRich Mar 16, 2023
01327a4
Merge branch 'main' of ssh://github.com/calcom/cal.com into 6507-cal-…
alannnc Mar 21, 2023
5f0559e
Move everything to components and add context
alannnc Mar 22, 2023
6ce8880
Merge branch 'main' of ssh://github.com/calcom/cal.com into 6507-cal-…
alannnc Mar 22, 2023
5eeff83
Fix select types using calcom ui one
alannnc Mar 22, 2023
6de52c2
Adding translations
alannnc Mar 22, 2023
9929504
Merge branch 'main' of ssh://github.com/calcom/cal.com into 6507-cal-…
alannnc Mar 22, 2023
b16eab2
Add missing translations
alannnc Mar 22, 2023
4cf508a
Add more translations
alannnc Mar 22, 2023
43a6821
min fix
alannnc Mar 22, 2023
552c5fb
Fixes for date select
alannnc Mar 22, 2023
6c68ad3
Prefer early return and mobile design fixes
alannnc Mar 22, 2023
e417e53
Fix style for mobile
alannnc Mar 23, 2023
175b928
Fix data with userId filter from popular events
alannnc Mar 23, 2023
da22675
add user id to average time duration
alannnc Mar 23, 2023
2f22aca
Merge branch 'main' into 6507-cal-803-analytics
PeerRich Mar 23, 2023
b986371
Merge branch 'main' into 6507-cal-803-analytics
zomars Mar 23, 2023
6dc346b
Removed submodules
zomars Mar 23, 2023
432f88a
Delete website
zomars Mar 23, 2023
b192ff7
Update yarn.lock
zomars Mar 23, 2023
fe2f86e
Code organization and type fixes
zomars Mar 23, 2023
32c7aa5
trpc fixes
zomars Mar 23, 2023
afdba4d
Builds are now passing
zomars Mar 23, 2023
b215e64
Relocates server code
zomars Mar 23, 2023
9d0b99c
Cleanup
zomars Mar 23, 2023
10a8a45
Merge branch 'main' into 6507-cal-803-analytics
zomars Mar 23, 2023
0925317
Medium size screen fixes
zomars Mar 23, 2023
a7dbbf9
Added TODO
zomars Mar 23, 2023
6578e5d
Merge branch 'main' into 6507-cal-803-analytics
zomars Mar 23, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/web/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ const nextConfig = {
skipDefaultConversion: true,
preventFullImport: true,
},
"@calcom/features/insights/components": {
transform: "@calcom/features/insights/components/{{member}}",
skipDefaultConversion: true,
preventFullImport: true,
},
lodash: {
transform: "lodash/{{member}}",
},
Expand Down
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/stripe-js": "^1.35.0",
"@tanstack/react-query": "^4.3.9",
"@tremor/react": "^2.0.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Latest tremor version

"@types/turndown": "^5.0.1",
"@vercel/edge-config": "^0.1.1",
"@vercel/edge-functions-ui": "^0.2.1",
Expand Down
116 changes: 116 additions & 0 deletions apps/web/pages/insights/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import {
zomars marked this conversation as resolved.
Show resolved Hide resolved
AverageEventDurationChart,
BookingKPICards,
BookingStatusLineChart,
LeastBookedTeamMembersTable,
MostBookedTeamMembersTable,
PopularEventsTable,
} from "@calcom/features/insights/components";
import { FiltersProvider } from "@calcom/features/insights/context/FiltersProvider";
import { useFilterContext } from "@calcom/features/insights/context/provider";
import { Filters } from "@calcom/features/insights/filters";
import Shell from "@calcom/features/shell/Shell";
import { UpgradeTip } from "@calcom/features/tips";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc";
import { Button, ButtonGroup } from "@calcom/ui";
import { FiRefreshCcw, FiUserPlus, FiUsers } from "@calcom/ui/components/icon";

const Heading = () => {
const { t } = useLocale();
const {
filter: { selectedTeamName },
} = useFilterContext();
return (
<div className="min-w-52">
<p className="text-lg font-semibold">
{t("analytics_for_organisation", {
organisationName: selectedTeamName,
})}
</p>
<p>{t("subtitle_analytics")}</p>
</div>
);
};

export default function InsightsPage() {
const { t } = useLocale();
const { data: user } = trpc.viewer.me.useQuery();
const features = [
{
icon: <FiUsers className="h-5 w-5 text-red-500" />,
title: t("view_bookings_across"),
description: t("view_bookings_across_description"),
},
{
icon: <FiRefreshCcw className="h-5 w-5 text-blue-500" />,
title: t("identify_booking_trends"),
description: t("identify_booking_trends_description"),
},
{
icon: <FiUserPlus className="h-5 w-5 text-green-500" />,
title: t("spot_popular_event_types"),
description: t("spot_popular_event_types_description"),
},
];

return (
<div>
<Shell>
<UpgradeTip
title={t("make_informed_decisions")}
description={t("make_informed_decisions_description")}
features={features}
background="/banners/insights.jpg"
buttons={
<div className="space-y-2 rtl:space-x-reverse sm:space-x-2">
<ButtonGroup>
<Button color="primary" href={`${WEBAPP_URL}/settings/teams/new`}>
{t("create_team")}
</Button>
<Button color="secondary" href="https://go.cal.com/insights" target="_blank">
{t("learn_more")}
</Button>
</ButtonGroup>
</div>
}>
{!user ? (
<></>
) : (
<FiltersProvider>
<div className="mb-4 ml-auto flex w-full flex-wrap justify-between">
<Heading />
<Filters />
</div>
<div className="mb-4 space-y-6">
<BookingKPICards />

<BookingStatusLineChart />

<div className="grid grid-cols-1 gap-5 md:grid-cols-2">
<PopularEventsTable />

<AverageEventDurationChart />
</div>
<div className="grid grid-cols-1 gap-5 md:grid-cols-2">
<MostBookedTeamMembersTable />
<LeastBookedTeamMembersTable />
</div>
<small className="block text-center text-gray-600">
{t("looking_for_more_analytics")}
<a
className="text-blue-500 hover:underline"
href="mailto:updates@cal.com?subject=Feature%20Request%3A%20More%20Analytics&body=Hey%20Cal.com%20Team%2C%20I%20love%20the%20analytics%20page%20but%20I%20am%20looking%20for%20...">
{" "}
{t("contact_support")}
</a>
</small>
</div>
</FiltersProvider>
)}
</UpgradeTip>
</Shell>
</div>
);
}
Binary file added apps/web/public/banners/insights.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/banners/routing-forms.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/web/public/banners/teams.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
45 changes: 36 additions & 9 deletions apps/web/public/static/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
"hidden_team_owner_message": "You need a pro account to use teams, you are hidden until you upgrade.",
"link_expires": "p.s. It expires in {{expiresIn}} hours.",
"upgrade_to_per_seat": "Upgrade to Per-Seat",
"seat_options_doesnt_support_confirmation":"Seats option doesn't support confirmation requirement",
"seat_options_doesnt_support_confirmation": "Seats option doesn't support confirmation requirement",
"team_upgrade_seats_details": "Of the {{memberCount}} members in your team, {{unpaidCount}} seat(s) are unpaid. At ${{seatPrice}}/month per seat the estimated total cost of your membership is ${{totalCost}}/month.",
"team_upgrade_banner_description": "Thank you for trialing our new team plan. We noticed your team \"{{teamName}}\" needs to be upgraded.",
"team_upgrade_banner_action": "Upgrade here",
Expand Down Expand Up @@ -433,7 +433,7 @@
"password_hint_min": "Minimum 7 characters long",
"password_hint_admin_min": "Minimum 15 characters long",
"password_hint_num": "Contain at least 1 number",
"max_limit_allowed_hint":"Must be {{limit}} or fewer characters long",
"max_limit_allowed_hint": "Must be {{limit}} or fewer characters long",
"invalid_password_hint": "The password must be a minimum of {{passwordLength}} characters long containing at least one number and have a mixture of uppercase and lowercase letters",
"incorrect_password": "Password is incorrect.",
"incorrect_username_password": "Username or password is incorrect.",
Expand Down Expand Up @@ -863,7 +863,7 @@
"add_new_calendar": "Add a new calendar",
"set_calendar": "Set where to add new events to when you're booked.",
"delete_schedule": "Delete schedule",
"delete_schedule_description":"Deleting a schedule will remove it from all event types. This action cannot be undone.",
"delete_schedule_description": "Deleting a schedule will remove it from all event types. This action cannot be undone.",
"schedule_created_successfully": "{{scheduleName}} schedule created successfully",
"availability_updated_successfully": "{{scheduleName}} schedule updated successfully",
"schedule_deleted_successfully": "Schedule deleted successfully",
Expand Down Expand Up @@ -1631,22 +1631,30 @@
"a_routing_form": "A Routing Form",
"form_description_placeholder": "Form Description",
"keep_me_connected_with_form": "Keep me connected with the form",
"fields_in_form_duplicated":"Any changes in Router and Fields of the form being duplicated, would reflect in the duplicate.",
"fields_in_form_duplicated": "Any changes in Router and Fields of the form being duplicated, would reflect in the duplicate.",
"form_deleted": "Form deleted",
"delete_form": "Are you sure you want to delete this form?",
"delete_form_action": "Yes, delete Form",
"delete_form_confirmation":"Anyone who you've shared the link with will no longer be able to access it.",
"delete_form_confirmation_2":"All associated responses will be deleted.",
"delete_form_confirmation": "Anyone who you've shared the link with will no longer be able to access it.",
"delete_form_confirmation_2": "All associated responses will be deleted.",
"typeform_redirect_url_copied": "Typeform Redirect URL copied! You can go and set the URL in Typeform form.",
"modifications_in_fields_warning": "Modifications in fields and routes of following forms will be reflected in this form.",
"connected_forms": "Connected Forms",
"form_modifications_warning": "Following forms would be affected when you modify fields or routes here.",
"responses_collection_waiting_description": "Wait for some time for responses to be collected. You can go and submit the form yourself as well.",
"this_is_what_your_users_would_see":"This is what your users would see",
"this_is_what_your_users_would_see": "This is what your users would see",
"identifies_name_field": "Identifies field by this name.",
"add_1_option_per_line": "Add 1 option per line",
"select_a_router": "Select a router",
"add_a_new_route": "Add a new Route",
"make_informed_decisions": "Make informed decisions with Insights",
"make_informed_decisions_description": "Our Insights dashboard surfaces all activity across your team and shows you trends that enable better team scheduling and decision making.",
"view_bookings_across": "View bookings across all members",
"view_bookings_across_description": "See who’s receiving the most bookings and ensure the best distribution across your team",
"identify_booking_trends": "Identify booking trends",
"identify_booking_trends_description": "See what times of the week and what times during the day are popular for your bookers",
"spot_popular_event_types": "Spot popular event types",
"spot_popular_event_types_description": "See which of your event types are receiving the most clicks and bookings",
"no_responses_yet": "No responses yet",
"this_will_be_the_placeholder": "This will be the placeholder",
"this_meeting_has_not_started_yet": "This meeting has not started yet",
Expand All @@ -1657,8 +1665,27 @@
"verification_code": "Verification code",
"can_you_try_again": "Can you try again with a different time?",
"verify": "Verify",
"invalid_event_name_variables":"There is an invalid variable in your event name",
"invalid_event_name_variables": "There is an invalid variable in your event name",
"select_all": "Select All",
"default_conferencing_bulk_title": "Bulk update existing event types",
"default_conferencing_bulk_description": "Update the locations for the selected event types"
"default_conferencing_bulk_description": "Update the locations for the selected event types",
"looking_for_more_analytics": "Looking for more analytics?",
"add_filter": "Add filter",
"select_user": "Select User",
"select_event_type": "Select Event Type",
"select_date_range": "Select Date Range",
"popular_events": "Popular Events",
"no_event_types_found": "No event types found",
"average_event_duration": "Average Event Duration",
"most_booked_members": "Most Booked Members",
"least_booked_members": "Least Booked Members",
"events_created": "Events Created",
"events_completed": "Events Completed",
"events_cancelled": "Events Cancelled",
"events_rescheduled": "Events Rescheduled",
"from_last_period": "from last period",
"from_to_date_period": "From: {{startDate}} To: {{endDate}}",
"analytics_for_organisation": "Analytics for {{organisationName}}",
"subtitle_analytics": "This is a organisation analytics",
"event_trends": "Event Trends"
}
Binary file removed apps/web/public/team-banner-background.jpg
Binary file not shown.
2 changes: 1 addition & 1 deletion apps/web/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ const base = require("@calcom/config/tailwind-preset");
/** @type {import('tailwindcss').Config} */
module.exports = {
...base,
content: [...base.content],
content: [...base.content, "../../node_modules/@tremor/**/*.{js,ts,jsx,tsx}"],
};
11 changes: 3 additions & 8 deletions packages/app-store/routing-forms/pages/forms/[...appPages].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,23 +99,18 @@ export default function RoutingForms({
CTA={hasPaidPlan && <NewFormButton />}
subtitle={t("routing_forms_description")}>
<UpgradeTip
dark
title={t("teams_plan_required")}
description={t("routing_forms_are_a_great_way")}
features={features}
background="/routing-form-banner-background.jpg"
background="/banners/routing-forms.jpg"
isParentLoading={isLoading && <SkeletonLoaderTeamList />}
buttons={
<div className="space-y-2 rtl:space-x-reverse sm:space-x-2">
<ButtonGroup>
<Button color="secondary" href={`${WEBAPP_URL}/settings/teams/new`}>
<Button color="primary" href={`${WEBAPP_URL}/settings/teams/new`}>
{t("upgrade")}
</Button>
<Button
color="minimal"
className="!bg-transparent text-white opacity-50 hover:opacity-100"
href="https://go.cal.com/teams-video"
target="_blank">
<Button color="minimal" href="https://go.cal.com/teams-video" target="_blank">
{t("learn_more")}
</Button>
</ButtonGroup>
Expand Down
4 changes: 2 additions & 2 deletions packages/features/ee/teams/components/TeamsListing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ export function TeamsListing() {
emptyTitle="no_teams"
emptyDescription="no_teams_description"
features={features}
background="/team-banner-background.jpg"
background="/banners/teams.jpg"
buttons={
<div className="space-y-2 rtl:space-x-reverse sm:space-x-2">
<ButtonGroup>
<Button color="primary" href={`${WEBAPP_URL}/settings/teams/new`}>
{t("create_team")}
</Button>
<Button color="secondary" href="https://go.cal.com/teams-video" target="_blank">
<Button color="minimal" href="https://go.cal.com/teams-video" target="_blank">
{t("learn_more")}
</Button>
</ButtonGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Card, LineChart, Title } from "@tremor/react";

import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc";

import { useFilterContext } from "../context/provider";
import { valueFormatter } from "../lib/valueFormatter";

export const AverageEventDurationChart = () => {
const { t } = useLocale();
const { filter } = useFilterContext();
const { dateRange, selectedUserId } = filter;
const [startDate, endDate] = dateRange;
const { selectedTeamId: teamId } = filter;

const { data, isSuccess } = trpc.viewer.insights.averageEventDuration.useQuery({
startDate: startDate.toISOString(),
endDate: endDate.toISOString(),
teamId,
userId: selectedUserId ?? undefined,
});

if (!isSuccess || data?.length == 0 || !startDate || !endDate || !teamId) return null;

return (
<Card>
<Title>{t("average_event_duration")}</Title>
<LineChart
className="mt-4 h-80"
data={data}
index="Date"
categories={["Average"]}
colors={["blue"]}
valueFormatter={valueFormatter}
/>
</Card>
);
};
62 changes: 62 additions & 0 deletions packages/features/insights/components/BookingKPICards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Grid } from "@tremor/react";

import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc";

import { useFilterContext } from "../context/provider";
import { KPICard } from "./KPICard";

export const BookingKPICards = () => {
const { t } = useLocale();
const { filter } = useFilterContext();
const { dateRange, selectedEventTypeId, selectedUserId } = filter;
const [startDate, endDate] = dateRange;

const { selectedTeamId: teamId } = filter;

const { data, isSuccess } = trpc.viewer.insights.eventsByStatus.useQuery({
startDate: startDate.toISOString(),
endDate: endDate.toISOString(),
teamId,
eventTypeId: selectedEventTypeId ?? undefined,
userId: selectedUserId ?? undefined,
});

const categories: {
title: string;
index: "created" | "completed" | "rescheduled" | "cancelled";
}[] = [
{
title: t("events_created"),
index: "created",
},
{
title: t("events_completed"),
index: "completed",
},
{
title: t("events_rescheduled"),
index: "rescheduled",
},
{
title: t("events_cancelled"),
index: "cancelled",
},
];

if (!isSuccess || !startDate || !endDate || !teamId || data?.empty) return null;

return (
<Grid numColsSm={2} numColsLg={4} className="gap-x-6 gap-y-6">
{categories.map((item) => (
<KPICard
key={item.title}
title={item.title}
value={data[item.index].count}
previousMetricData={data[item.index]}
previousDateRange={data.previousRange}
/>
))}
</Grid>
);
};