From 0fb531e4b7504e97739ed545a61ba2cbf9fb9444 Mon Sep 17 00:00:00 2001 From: gurusainath Date: Fri, 9 Feb 2024 22:16:17 +0530 Subject: [PATCH] chore: ui and filter store updates --- web/components/view/all-issues-root.tsx | 64 ++++---- .../view/applied-filters/filter.tsx | 17 ++- web/components/view/applied-filters/root.tsx | 33 +++-- web/components/view/filters/dropdown.tsx | 28 +++- .../view/filters/filter-item-root.tsx | 45 +++++- .../view/filters/filter-selection.tsx | 9 +- web/components/view/filters/root.tsx | 14 +- web/components/view/types.d.ts | 2 +- .../view/views/create-edit-form.tsx | 33 +++-- web/components/view/views/root.tsx | 78 +++++----- web/constants/view/filters.ts | 11 +- web/hooks/store/views/use-view-filters.tsx | 137 ++++++++++++++++-- .../[projectId]/views/private/[viewId].tsx | 53 +++++++ .../[projectId]/views/private/index.tsx | 53 +++++++ .../[projectId]/views/public/[viewId].tsx | 54 +++++++ .../views/private/[viewId].tsx | 25 +++- .../[workspaceSlug]/views/public/[viewId].tsx | 25 +++- 17 files changed, 554 insertions(+), 127 deletions(-) create mode 100644 web/pages/[workspaceSlug]/projects/[projectId]/views/private/[viewId].tsx create mode 100644 web/pages/[workspaceSlug]/projects/[projectId]/views/private/index.tsx create mode 100644 web/pages/[workspaceSlug]/projects/[projectId]/views/public/[viewId].tsx diff --git a/web/components/view/all-issues-root.tsx b/web/components/view/all-issues-root.tsx index 65422042b..178498280 100644 --- a/web/components/view/all-issues-root.tsx +++ b/web/components/view/all-issues-root.tsx @@ -1,7 +1,7 @@ import { FC, Fragment, useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { observer } from "mobx-react-lite"; -import { CheckCircle, Pencil } from "lucide-react"; +import { CheckCircle, ChevronDown, ChevronUp, Pencil } from "lucide-react"; // hooks import { useView, useViewDetail } from "hooks/store"; import useToast from "hooks/use-toast"; @@ -19,7 +19,7 @@ import { // ui import { Spinner } from "@plane/ui"; // constants -import { VIEW_TYPES, viewLocalPayload } from "constants/view"; +import { viewLocalPayload } from "constants/view"; // types import { TViewOperations } from "./types"; import { TView, TViewFilters, TViewDisplayFilters, TViewDisplayProperties, TViewTypes } from "@plane/types"; @@ -30,6 +30,7 @@ type TAllIssuesViewRoot = { viewId: string; viewType: TViewTypes; baseRoute: string; + workspaceViewTabOptions: { key: TViewTypes; title: string; href: string }[]; }; type TViewOperationsToggle = { @@ -38,7 +39,7 @@ type TViewOperationsToggle = { }; export const AllIssuesViewRoot: FC = observer((props) => { - const { workspaceSlug, projectId, viewId, viewType, baseRoute } = props; + const { workspaceSlug, projectId, viewId, viewType, baseRoute, workspaceViewTabOptions } = props; // hooks const viewStore = useView(workspaceSlug, projectId, viewType); const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); @@ -51,28 +52,22 @@ export const AllIssuesViewRoot: FC = observer((props) => { const handleViewOperationsToggle = (type: TViewOperationsToggle["type"], viewId: string | undefined) => setViewOperationsToggle({ type, viewId }); - const workspaceViewTabOptions = useMemo( - () => [ - { - key: VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS, - title: "Private", - href: `/${workspaceSlug}/views/private/assigned`, - }, - { - key: VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS, - title: "Public", - href: `/${workspaceSlug}/views/public/all-issues`, - }, - ], - [workspaceSlug] + const viewDetailCreateStore = useViewDetail( + workspaceSlug, + projectId, + viewOperationsToggle?.viewId || viewId, + viewType ); const viewOperations: TViewOperations = useMemo( () => ({ setName: (name: string) => viewDetailStore?.setName(name), setDescription: (name: string) => viewDetailStore?.setDescription(name), - setFilters: (filterKey: keyof TViewFilters, filterValue: "clear_all" | string) => - viewDetailStore?.setFilters(filterKey, filterValue), + setFilters: (filterKey: keyof TViewFilters | undefined, filterValue: "clear_all" | string) => { + if (viewOperationsToggle.type && ["CREATE", "EDIT"].includes(viewOperationsToggle.type)) + viewDetailCreateStore?.setFilters(filterKey, filterValue); + else viewDetailStore?.setFilters(filterKey, filterValue); + }, setDisplayFilters: (display_filters: Partial) => viewDetailStore?.setDisplayFilters(display_filters), setDisplayProperties: (displayPropertyKey: keyof TViewDisplayProperties) => @@ -114,17 +109,24 @@ export const AllIssuesViewRoot: FC = observer((props) => { } }, }), - [viewStore, viewDetailStore, setToastAlert] + [viewStore, viewDetailStore, setToastAlert, viewOperationsToggle, viewDetailCreateStore] ); + // fetch all issues useEffect(() => { const fetchViews = async () => { await viewStore?.fetch(viewStore?.viewIds.length > 0 ? "mutation-loader" : "init-loader"); + }; + if (workspaceSlug && viewType && viewStore) fetchViews(); + }, [workspaceSlug, projectId, viewType, viewStore]); + + // fetch view by id + useEffect(() => { + const fetchViews = async () => { viewId && (await viewStore?.fetchById(viewId)); }; - if (workspaceSlug && viewId && viewType && viewStore) fetchViews(); - }, [workspaceSlug, viewId, viewType, viewStore]); + }, [workspaceSlug, projectId, viewId, viewType, viewStore]); return (
@@ -170,7 +172,7 @@ export const AllIssuesViewRoot: FC = observer((props) => { />
-
+
= observer((props) => { viewId={viewId} viewType={viewType} viewOperations={viewOperations} + propertyVisibleCount={5} />
@@ -198,7 +201,7 @@ export const AllIssuesViewRoot: FC = observer((props) => { viewId={viewId} viewType={viewType} viewOperations={viewOperations} - displayDropdownText={false} + displayDropdownText={true} />
@@ -209,15 +212,24 @@ export const AllIssuesViewRoot: FC = observer((props) => { viewId={viewId} viewType={viewType} viewOperations={viewOperations} - displayDropdownText={false} + displayDropdownText={true} />
-
+
+ +
+
+ Update +
+
+ +
+
)} diff --git a/web/components/view/applied-filters/filter.tsx b/web/components/view/applied-filters/filter.tsx index 868f091a1..7333fee2e 100644 --- a/web/components/view/applied-filters/filter.tsx +++ b/web/components/view/applied-filters/filter.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react-lite"; import isEmpty from "lodash/isEmpty"; import { X } from "lucide-react"; // hooks -import { useViewDetail } from "hooks/store"; +import { useViewDetail, useViewFilter } from "hooks/store"; // components import { ViewAppliedFiltersItem } from "./filter-item"; // types @@ -17,12 +17,16 @@ type TViewAppliedFilters = { viewType: TViewTypes; filterKey: keyof TViewFilters; viewOperations: TViewOperations; + propertyVisibleCount?: number | undefined; }; export const ViewAppliedFilters: FC = observer((props) => { - const { workspaceSlug, projectId, viewId, viewType, filterKey, viewOperations } = props; - + const { workspaceSlug, projectId, viewId, viewType, filterKey, viewOperations, propertyVisibleCount } = props; + // hooks const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); + const viewFilterStore = useViewFilter(workspaceSlug, projectId); + + const currentDefaultFilterDetails = viewFilterStore?.propertyDefaultDetails(filterKey); const propertyValues = viewDetailStore?.appliedFilters?.filters && !isEmpty(viewDetailStore?.appliedFilters?.filters) @@ -36,9 +40,12 @@ export const ViewAppliedFilters: FC = observer((props) => {
{filterKey.replaceAll("_", " ")}
- {propertyValues.length >= 100 ? ( + {propertyVisibleCount && propertyValues.length >= propertyVisibleCount ? (
- {propertyValues.length} {filterKey.replaceAll("_", " ")}s +
{currentDefaultFilterDetails?.icon}
+
+ {propertyValues.length} {currentDefaultFilterDetails?.label} +
) : ( <> diff --git a/web/components/view/applied-filters/root.tsx b/web/components/view/applied-filters/root.tsx index 855667c8f..3e1d304ac 100644 --- a/web/components/view/applied-filters/root.tsx +++ b/web/components/view/applied-filters/root.tsx @@ -16,10 +16,20 @@ type TViewAppliedFiltersRoot = { viewId: string; viewType: TViewTypes; viewOperations: TViewOperations; + propertyVisibleCount?: number | undefined; + showClearAll?: boolean; }; export const ViewAppliedFiltersRoot: FC = observer((props) => { - const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props; + const { + workspaceSlug, + projectId, + viewId, + viewType, + viewOperations, + propertyVisibleCount = undefined, + showClearAll = false, + } = props; // hooks const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); @@ -28,7 +38,7 @@ export const ViewAppliedFiltersRoot: FC = observer((pro ? Object.keys(viewDetailStore?.appliedFilters?.filters) : undefined; - const clearAllFilters = () => viewDetailStore?.setFilters(undefined, "clear_all"); + const clearAllFilters = () => viewOperations?.setFilters(undefined, "clear_all"); if (!filterKeys || !viewDetailStore?.isFiltersApplied) return ( @@ -49,20 +59,23 @@ export const ViewAppliedFiltersRoot: FC = observer((pro viewType={viewType} filterKey={filterKey} viewOperations={viewOperations} + propertyVisibleCount={propertyVisibleCount} /> ); })} -
-
Clear All
-
- + {showClearAll && ( +
+
Clear All
+
+ +
-
+ )}
); }); diff --git a/web/components/view/filters/dropdown.tsx b/web/components/view/filters/dropdown.tsx index 8a0a0b1af..48d92c3fa 100644 --- a/web/components/view/filters/dropdown.tsx +++ b/web/components/view/filters/dropdown.tsx @@ -2,6 +2,7 @@ import { FC, Fragment, ReactNode, useRef, useState } from "react"; import { observer } from "mobx-react-lite"; import { Combobox } from "@headlessui/react"; import { usePopper } from "react-popper"; +import { Placement } from "@popperjs/core"; import { ListFilter, Search } from "lucide-react"; // hooks import useOutsideClickDetector from "hooks/use-outside-click-detector"; @@ -21,13 +22,24 @@ type TViewFiltersDropdown = { viewOperations: TViewOperations; children?: ReactNode; displayDropdownText?: boolean; + dropdownPlacement?: Placement; }; export const ViewFiltersDropdown: FC = observer((props) => { - const { workspaceSlug, projectId, viewId, viewType, viewOperations, children, displayDropdownText = true } = props; + const { + workspaceSlug, + projectId, + viewId, + viewType, + viewOperations, + children, + displayDropdownText = true, + dropdownPlacement = "bottom-start", + } = props; // state const [dropdownToggle, setDropdownToggle] = useState(false); const [query, setQuery] = useState(""); + const [dateCustomFilterToggle, setDateCustomFilterToggle] = useState(undefined); // refs const dropdownRef = useRef(null); // popper-js refs @@ -35,7 +47,7 @@ export const ViewFiltersDropdown: FC = observer((props) => const [popperElement, setPopperElement] = useState(null); // popper-js init const { styles, attributes } = usePopper(referenceElement, popperElement, { - placement: "bottom-start", + placement: dropdownPlacement, modifiers: [ { name: "preventOverflow", @@ -43,6 +55,12 @@ export const ViewFiltersDropdown: FC = observer((props) => padding: 12, }, }, + { + name: "offset", + options: { + offset: [0, 10], + }, + }, ], }); @@ -55,7 +73,7 @@ export const ViewFiltersDropdown: FC = observer((props) => else handleDropdownClose(); }; - useOutsideClickDetector(dropdownRef, handleDropdownClose); + useOutsideClickDetector(dropdownRef, () => dateCustomFilterToggle === undefined && handleDropdownClose()); return ( @@ -109,13 +127,15 @@ export const ViewFiltersDropdown: FC = observer((props) => />
-
+
diff --git a/web/components/view/filters/filter-item-root.tsx b/web/components/view/filters/filter-item-root.tsx index 0f37d4b40..810fcd1ea 100644 --- a/web/components/view/filters/filter-item-root.tsx +++ b/web/components/view/filters/filter-item-root.tsx @@ -1,9 +1,10 @@ import { FC, useState } from "react"; import { observer } from "mobx-react-lite"; // hooks -import { useViewFilter } from "hooks/store"; +import { useViewDetail, useViewFilter } from "hooks/store"; // components import { ViewFiltersItem, ViewFilterSelection } from "../"; +import { DateFilterModal } from "components/core"; // types import { TViewOperations } from "../types"; import { TViewFilters, TViewTypes } from "@plane/types"; @@ -15,11 +16,23 @@ type TViewFiltersItemRoot = { viewType: TViewTypes; viewOperations: TViewOperations; filterKey: keyof TViewFilters; + dateCustomFilterToggle: string | undefined; + setDateCustomFilterToggle: (value: string | undefined) => void; }; export const ViewFiltersItemRoot: FC = observer((props) => { - const { workspaceSlug, projectId, viewId, viewType, viewOperations, filterKey } = props; + const { + workspaceSlug, + projectId, + viewId, + viewType, + viewOperations, + filterKey, + dateCustomFilterToggle, + setDateCustomFilterToggle, + } = props; // hooks + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); const viewFilterHelper = useViewFilter(workspaceSlug, projectId); // state const [viewAll, setViewAll] = useState(false); @@ -28,7 +41,24 @@ export const ViewFiltersItemRoot: FC = observer((props) => const filterPropertyIds = propertyIds.length > 5 ? (viewAll ? propertyIds : propertyIds.slice(0, 5)) : propertyIds; - const handlePropertySelection = (_propertyId: string) => viewOperations?.setFilters(filterKey, _propertyId); + const handlePropertySelection = (_propertyId: string) => { + if (["start_date", "target_date"].includes(filterKey)) { + if (_propertyId === "custom") { + const _propertyIds = viewDetailStore?.appliedFilters?.filters?.[filterKey] || []; + const selectedDates = _propertyIds.filter((id) => id.includes("-")); + if (selectedDates.length > 0) + selectedDates.forEach((date: string) => viewOperations?.setFilters(filterKey, date)); + else setDateCustomFilterToggle(filterKey); + } else viewOperations?.setFilters(filterKey, _propertyId); + } else viewOperations?.setFilters(filterKey, _propertyId); + }; + + const handleCustomDateSelection = (selectedDates: string[]) => { + selectedDates.forEach((date: string) => { + viewOperations?.setFilters(filterKey, date); + setDateCustomFilterToggle(undefined); + }); + }; if (propertyIds.length <= 0) return
No items are available.
; @@ -65,6 +95,15 @@ export const ViewFiltersItemRoot: FC = observer((props) => {viewAll ? "View less" : "View all"}
)} + + {dateCustomFilterToggle === filterKey && ( + setDateCustomFilterToggle(undefined)} + isOpen={dateCustomFilterToggle === filterKey ? true : false} + onSelect={handleCustomDateSelection} + title="Start date" + /> + )} ); }); diff --git a/web/components/view/filters/filter-selection.tsx b/web/components/view/filters/filter-selection.tsx index c658d7d48..8fb0f0483 100644 --- a/web/components/view/filters/filter-selection.tsx +++ b/web/components/view/filters/filter-selection.tsx @@ -21,7 +21,14 @@ export const ViewFilterSelection: FC = observer((props) => const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); const propertyIds = viewDetailStore?.appliedFilters?.filters?.[filterKey] || []; - const isSelected = propertyIds?.includes(propertyId) || false; + + const isSelected = ["start_date", "target_date"].includes(filterKey) + ? propertyId === "custom" + ? propertyIds.filter((id) => id.includes("-")).length > 0 + ? true + : false + : propertyIds?.includes(propertyId) + : propertyIds?.includes(propertyId) || false; return (
void; }; export const ViewFiltersRoot: FC = observer((props) => { - const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props; + const { + workspaceSlug, + projectId, + viewId, + viewType, + viewOperations, + dateCustomFilterToggle, + setDateCustomFilterToggle, + } = props; // hooks const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); // state @@ -62,6 +72,8 @@ export const ViewFiltersRoot: FC = observer((props) => { viewType={viewType} viewOperations={viewOperations} filterKey={filterKey} + dateCustomFilterToggle={dateCustomFilterToggle} + setDateCustomFilterToggle={setDateCustomFilterToggle} /> )}
diff --git a/web/components/view/types.d.ts b/web/components/view/types.d.ts index 1330a18e1..f3fff5a56 100644 --- a/web/components/view/types.d.ts +++ b/web/components/view/types.d.ts @@ -3,7 +3,7 @@ import { TView, TViewFilters, TViewDisplayFilters, TViewDisplayProperties } from export type TViewOperations = { setName: (name: string) => void; setDescription: (description: string) => void; - setFilters: (filterKey: keyof TViewFilters, filterValue: "clear_all" | string) => void; + setFilters: (filterKey: keyof TViewFilters | undefined, filterValue: "clear_all" | string) => void; setDisplayFilters: (display_filters: Partial) => void; setDisplayProperties: (displayPropertyKey: keyof TViewDisplayProperties) => void; diff --git a/web/components/view/views/create-edit-form.tsx b/web/components/view/views/create-edit-form.tsx index 78ce4fd93..63dec16e8 100644 --- a/web/components/view/views/create-edit-form.tsx +++ b/web/components/view/views/create-edit-form.tsx @@ -5,7 +5,7 @@ import { Briefcase, Globe2, Plus, X } from "lucide-react"; // hooks import { useViewDetail, useProject } from "hooks/store"; // components -import { ViewAppliedFiltersRoot } from "../"; +import { ViewAppliedFiltersRoot, ViewFiltersDropdown } from "../"; // ui import { Input, Button } from "@plane/ui"; // types @@ -85,7 +85,7 @@ export const ViewCreateEditForm: FC = observer((props) => { leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - +
{projectId && projectDetails ? (
@@ -121,13 +121,27 @@ export const ViewCreateEditForm: FC = observer((props) => {
-
-
- + +
+
+ +
+
Filters
-
Filters
-
-
+ +
{ + viewOperations.setFilters(undefined, "clear_all"); + }} + >
Clear all filters
@@ -135,13 +149,14 @@ export const ViewCreateEditForm: FC = observer((props) => {
-
+
diff --git a/web/components/view/views/root.tsx b/web/components/view/views/root.tsx index abdc417e0..68881c616 100644 --- a/web/components/view/views/root.tsx +++ b/web/components/view/views/root.tsx @@ -64,48 +64,46 @@ export const ViewRoot: FC = observer((props) => { return (
- {viewStore?.viewIds && viewStore?.viewIds.length > 0 && ( -
- {viewIds.map((_viewId) => ( - - - - ))} +
+ {viewStore?.viewIds && viewStore?.viewIds.length > 0 && ( +
+ {viewIds.map((_viewId) => ( + + + + ))} -
- {viewStore?.viewIds.length <= (itemsToRenderViewsCount || viewStore?.viewIds.length) ? null : ( - -
- - - - - {viewStore?.viewIds.length - (itemsToRenderViewsCount || viewStore?.viewIds.length)} More... - -
-
- )} +
+ {viewStore?.viewIds.length <= (itemsToRenderViewsCount || viewStore?.viewIds.length) ? null : ( + +
+ + + + + {viewStore?.viewIds.length - (itemsToRenderViewsCount || viewStore?.viewIds.length)} More... + +
+
+ )} +
-
- )} + )} +
); }; -GlobalViewIssuesPage.getLayout = function getLayout(page: ReactElement) { +WorkspacePrivateViewPage.getLayout = function getLayout(page: ReactElement) { return }>{page}; }; -export default GlobalViewIssuesPage; +export default WorkspacePrivateViewPage; diff --git a/web/pages/[workspaceSlug]/views/public/[viewId].tsx b/web/pages/[workspaceSlug]/views/public/[viewId].tsx index 838e45345..7d1aa959b 100644 --- a/web/pages/[workspaceSlug]/views/public/[viewId].tsx +++ b/web/pages/[workspaceSlug]/views/public/[viewId].tsx @@ -1,4 +1,4 @@ -import { ReactElement } from "react"; +import { ReactElement, useMemo } from "react"; import { useRouter } from "next/router"; // layouts import { AppLayout } from "layouts/app-layout"; @@ -9,10 +9,26 @@ import { NextPageWithLayout } from "lib/types"; // constants import { VIEW_TYPES } from "constants/view"; -const GlobalViewIssuesPage: NextPageWithLayout = () => { +const WorkspacePublicViewPage: NextPageWithLayout = () => { const router = useRouter(); const { workspaceSlug, viewId } = router.query; + const workspaceViewTabOptions = useMemo( + () => [ + { + key: VIEW_TYPES.WORKSPACE_PRIVATE_VIEWS, + title: "Private", + href: `/${workspaceSlug}/views/private/assigned`, + }, + { + key: VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS, + title: "Public", + href: `/${workspaceSlug}/views/public/all-issues`, + }, + ], + [workspaceSlug] + ); + if (!workspaceSlug || !viewId) return <>; return (
@@ -23,6 +39,7 @@ const GlobalViewIssuesPage: NextPageWithLayout = () => { viewId={viewId.toString()} viewType={VIEW_TYPES.WORKSPACE_PUBLIC_VIEWS} baseRoute={`/${workspaceSlug?.toString()}/views/public`} + workspaceViewTabOptions={workspaceViewTabOptions} />
Issues render
@@ -30,8 +47,8 @@ const GlobalViewIssuesPage: NextPageWithLayout = () => { ); }; -GlobalViewIssuesPage.getLayout = function getLayout(page: ReactElement) { +WorkspacePublicViewPage.getLayout = function getLayout(page: ReactElement) { return }>{page}; }; -export default GlobalViewIssuesPage; +export default WorkspacePublicViewPage;