diff --git a/apps/app/components/core/issues-view-filter.tsx b/apps/app/components/core/issues-view-filter.tsx index bff7113d3..f959688af 100644 --- a/apps/app/components/core/issues-view-filter.tsx +++ b/apps/app/components/core/issues-view-filter.tsx @@ -97,26 +97,57 @@ export const IssuesFilterView: React.FC = () => { + label={ + Filters - } - optionsPosition="right" > - - setFilters({ - assignees: ["72d6ad43-41ff-4907-9980-2f5ee8745ad3"], - }) - } - > - Member- Aaryan - +

Status

+ {statesList?.map((state) => ( + { + const filterStates = filters?.state ?? []; + const newFilterState = filterStates.includes(state.id) + ? filterStates.filter((id) => id !== state.id) + : [...filterStates, state.id]; + setFilters({ ...filters, state: newFilterState }); + }} + > + <>{state.name} + + ))} +

Members

+ {members?.map((member) => ( + {}}> + <> + {member.member.first_name && member.member.first_name !== "" + ? member.member.first_name + " " + member.member.last_name + : member.member.email} + + + ))} +

Labels

+ {issueLabels?.map((label) => ( + {}}> + <>{label.name} + + ))} +

Priority

+ {PRIORITIES?.map((priority) => ( + { + if (priority === null) return; + const filterPriorities = filters?.priority ?? []; + const newFilterPriority = filterPriorities.includes(priority) + ? filterPriorities.filter((id) => id !== priority) + : [...filterPriorities, priority]; + setFilters({ ...filters, priority: newFilterPriority }); + }} + > + {priority ?? "None"} + + ))}
{({ open }) => ( diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx index a6357117c..3f9a64a9f 100644 --- a/apps/app/components/core/issues-view.tsx +++ b/apps/app/components/core/issues-view.tsx @@ -17,12 +17,20 @@ import useIssuesView from "hooks/use-issues-view"; import { AllLists, AllBoards } from "components/core"; import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues"; import StrictModeDroppable from "components/dnd/StrictModeDroppable"; +import { CreateUpdateViewModal } from "components/views"; // icons -import { PlusIcon, RectangleStackIcon, TrashIcon } from "@heroicons/react/24/outline"; +import { PlusIcon, RectangleStackIcon, TrashIcon, XMarkIcon } from "@heroicons/react/24/outline"; // helpers import { getStatesList } from "helpers/state.helper"; // types -import { CycleIssueResponse, IIssue, ModuleIssueResponse, UserAuth } from "types"; +import { + CycleIssueResponse, + IIssue, + IIssueFilterOptions, + IView, + ModuleIssueResponse, + UserAuth, +} from "types"; // fetch-keys import { CYCLE_ISSUES, @@ -44,6 +52,7 @@ type Props = { export const IssuesView: React.FC = ({ type = "issue", openIssuesListModal, userAuth }) => { // create issue modal const [createIssueModal, setCreateIssueModal] = useState(false); + const [createViewModal, setCreateViewModal] = useState(null); const [preloadedData, setPreloadedData] = useState< (Partial & { actionType: "createIssue" | "edit" | "delete" }) | undefined >(undefined); @@ -360,6 +369,11 @@ export const IssuesView: React.FC = ({ type = "issue", openIssuesListModa return ( <> + setCreateViewModal(null)} + preLoadedData={createViewModal} + /> setCreateIssueModal(false)} @@ -378,24 +392,95 @@ export const IssuesView: React.FC = ({ type = "issue", openIssuesListModa isOpen={deleteIssueModal} data={issueToDelete} /> -
- {Object.keys(filters).map((key) => { - if (filters[key as keyof typeof filters] !== null) - return ( - - ); - })} +
+
+ {Object.keys(filters).map((key) => { + if (filters[key as keyof typeof filters] !== null) + return ( +
+

+ Filter for {key}:{" "} +

+ {filters[key as keyof IIssueFilterOptions] === null || + (filters[key as keyof IIssueFilterOptions]?.length ?? 0) <= 0 ? ( +

None

+ ) : ( + Array.isArray(filters[key as keyof IIssueFilterOptions]) && ( +

+ {key === "state" + ? (filters[key as keyof IIssueFilterOptions] as any)?.map( + (stateId: any) => { + const state = states?.find((s) => s.id === stateId); + return ( +

+ {state?.name ?? "Loading..."} + { + setFilters({ + ...filters, + [key]: ( + filters[key as keyof IIssueFilterOptions] as any + )?.filter((s: any) => s !== stateId), + }); + }} + > + + +

+ ); + } + ) + : key === "priority" + ? (filters[key as keyof IIssueFilterOptions] as any)?.map( + (priority: any) => ( +

+ {priority} + { + setFilters({ + ...filters, + [key]: ( + filters[key as keyof IIssueFilterOptions] as any + )?.filter((p: any) => p !== priority), + }); + }} + > + + +

+ ) + ) + : (filters[key as keyof IIssueFilterOptions] as any)?.join(", ")} +

+ ) + )} +
+ ); + })} +
+ +
+ +
diff --git a/apps/app/components/icons/index.ts b/apps/app/components/icons/index.ts index deeed5575..4adfbac5e 100644 --- a/apps/app/components/icons/index.ts +++ b/apps/app/components/icons/index.ts @@ -41,4 +41,5 @@ export * from "./assignment-clipboard-icon"; export * from "./tick-mark-icon"; export * from "./contrast-icon"; export * from "./people-group-icon"; -export * from "./cmd-icon"; \ No newline at end of file +export * from "./cmd-icon"; +export * from "./view-list-icon"; diff --git a/apps/app/components/icons/view-list-icon.tsx b/apps/app/components/icons/view-list-icon.tsx new file mode 100644 index 000000000..e893c2732 --- /dev/null +++ b/apps/app/components/icons/view-list-icon.tsx @@ -0,0 +1,24 @@ +import React from "react"; + +import type { Props } from "./types"; + +export const ViewListIcon: React.FC = ({ + width = "24", + height = "24", + className, + color = "#858E96", +}) => ( + + + +); diff --git a/apps/app/components/project/single-sidebar-project.tsx b/apps/app/components/project/single-sidebar-project.tsx index a89037328..a24956bac 100644 --- a/apps/app/components/project/single-sidebar-project.tsx +++ b/apps/app/components/project/single-sidebar-project.tsx @@ -7,7 +7,13 @@ import { Disclosure, Transition } from "@headlessui/react"; import { CustomMenu } from "components/ui"; // icons import { ChevronDownIcon } from "@heroicons/react/24/outline"; -import { ContrastIcon, LayerDiagonalIcon, PeopleGroupIcon, SettingIcon } from "components/icons"; +import { + ContrastIcon, + LayerDiagonalIcon, + PeopleGroupIcon, + SettingIcon, + ViewListIcon, +} from "components/icons"; // helpers import { truncateText } from "helpers/string.helper"; // types @@ -38,6 +44,11 @@ const navigation = (workspaceSlug: string, projectId: string) => [ href: `/${workspaceSlug}/projects/${projectId}/modules`, icon: PeopleGroupIcon, }, + { + name: "Views", + href: `/${workspaceSlug}/projects/${projectId}/views`, + icon: ViewListIcon, + }, { name: "Settings", href: `/${workspaceSlug}/projects/${projectId}/settings`, diff --git a/apps/app/components/views/delete-view-modal.tsx b/apps/app/components/views/delete-view-modal.tsx index a9688b4f5..3a25de86f 100644 --- a/apps/app/components/views/delete-view-modal.tsx +++ b/apps/app/components/views/delete-view-modal.tsx @@ -21,11 +21,12 @@ import { VIEWS_LIST } from "constants/fetch-keys"; type Props = { isOpen: boolean; - setIsOpen: React.Dispatch>; - data?: IView; + data: IView | null; + onClose: () => void; + onSuccess?: () => void; }; -export const DeleteViewModal: React.FC = ({ isOpen, setIsOpen, data }) => { +export const DeleteViewModal: React.FC = ({ isOpen, data, onClose, onSuccess }) => { const [isDeleteLoading, setIsDeleteLoading] = useState(false); const router = useRouter(); @@ -36,19 +37,23 @@ export const DeleteViewModal: React.FC = ({ isOpen, setIsOpen, data }) => const cancelButtonRef = useRef(null); const handleClose = () => { - setIsOpen(false); setIsDeleteLoading(false); + onClose(); }; const handleDeletion = async () => { setIsDeleteLoading(true); - if (!workspaceSlug || !data) return; + if (!workspaceSlug || !data || !projectId) return; await viewsService - .deleteView(projectId as string, data.id) + .deleteView(workspaceSlug as string, projectId as string, data.id) .then(() => { - mutate(VIEWS_LIST(projectId as string)); - router.push(`/${workspaceSlug}/projects/${projectId}/issues`); + mutate(VIEWS_LIST(projectId as string), (views) => + views?.filter((view) => view.id !== data.id) + ); + + if (onSuccess) onSuccess(); + handleClose(); setToastAlert({ diff --git a/apps/app/components/views/form.tsx b/apps/app/components/views/form.tsx index d331b17c3..aa0cc7199 100644 --- a/apps/app/components/views/form.tsx +++ b/apps/app/components/views/form.tsx @@ -12,6 +12,7 @@ type Props = { handleClose: () => void; status: boolean; data?: IView; + preLoadedData?: Partial | null; }; const defaultValues: Partial = { @@ -19,7 +20,13 @@ const defaultValues: Partial = { description: "", }; -export const ViewForm: React.FC = ({ handleFormSubmit, handleClose, status, data }) => { +export const ViewForm: React.FC = ({ + handleFormSubmit, + handleClose, + status, + data, + preLoadedData, +}) => { const { register, formState: { errors, isSubmitting }, @@ -44,6 +51,13 @@ export const ViewForm: React.FC = ({ handleFormSubmit, handleClose, statu }); }, [data, reset]); + useEffect(() => { + reset({ + ...defaultValues, + ...preLoadedData, + }); + }, [preLoadedData, reset]); + return (
diff --git a/apps/app/components/views/modal.tsx b/apps/app/components/views/modal.tsx index d3b49c6d7..1662da016 100644 --- a/apps/app/components/views/modal.tsx +++ b/apps/app/components/views/modal.tsx @@ -4,8 +4,6 @@ import { useRouter } from "next/router"; import { mutate } from "swr"; -// react-hook-form -import { useForm } from "react-hook-form"; // headless ui import { Dialog, Transition } from "@headlessui/react"; // services @@ -23,14 +21,15 @@ type Props = { isOpen: boolean; handleClose: () => void; data?: IView; + preLoadedData?: Partial | null; }; -const defaultValues: Partial = { - name: "", - description: "", -}; - -export const CreateUpdateViewModal: React.FC = ({ isOpen, handleClose, data }) => { +export const CreateUpdateViewModal: React.FC = ({ + isOpen, + handleClose, + data, + preLoadedData, +}) => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -38,14 +37,13 @@ export const CreateUpdateViewModal: React.FC = ({ isOpen, handleClose, da const onClose = () => { handleClose(); - reset(defaultValues); }; - const { reset } = useForm({ - defaultValues, - }); - const createView = async (payload: IView) => { + payload = { + ...payload, + query_data: payload.query, + }; await viewsService .createView(workspaceSlug as string, projectId as string, payload) .then(() => { @@ -137,6 +135,7 @@ export const CreateUpdateViewModal: React.FC = ({ isOpen, handleClose, da handleClose={handleClose} status={data ? true : false} data={data} + preLoadedData={preLoadedData} /> diff --git a/apps/app/contexts/issue-view.context.tsx b/apps/app/contexts/issue-view.context.tsx index c032d15d5..8cb43fb0d 100644 --- a/apps/app/contexts/issue-view.context.tsx +++ b/apps/app/contexts/issue-view.context.tsx @@ -8,6 +8,7 @@ import useSWR, { mutate } from "swr"; import ToastAlert from "components/toast-alert"; // services import projectService from "services/project.service"; +import viewsService from "services/views.service"; // types import { IIssueFilterOptions, IProjectMember, NestedKeyOf } from "types"; // fetch-keys @@ -16,6 +17,8 @@ import { MODULE_ISSUES_WITH_PARAMS, PROJECT_ISSUES_LIST_WITH_PARAMS, USER_PROJECT_VIEW, + VIEW_DETAILS, + VIEW_ISSUES, } from "constants/fetch-keys"; export const issueViewContext = createContext({} as ContextType); @@ -62,8 +65,10 @@ export const initialState: StateType = { orderBy: "created_at", filters: { type: null, + priority: null, assignees: null, labels: null, + state: null, issue__assignees__id: null, issue__labels__id: null, }, @@ -150,6 +155,17 @@ const saveDataToServer = async (workspaceSlug: string, projectID: string, state: }); }; +const sendFilterDataToServer = async ( + workspaceSlug: string, + projectID: string, + viewId: string, + state: any +) => { + await viewsService.patchView(workspaceSlug, projectID, viewId, { + ...state, + }); +}; + const setNewDefault = async (workspaceSlug: string, projectId: string, state: any) => { mutate( workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId as string) : null, @@ -174,7 +190,7 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = const [state, dispatch] = useReducer(reducer, initialState); const router = useRouter(); - const { workspaceSlug, projectId, cycleId, moduleId } = router.query; + const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; const { data: myViewProps, mutate: mutateMyViewProps } = useSWR( workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId as string) : null, @@ -183,6 +199,18 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = : null ); + const { data: viewDetails, mutate: mutateViewDetails } = useSWR( + workspaceSlug && projectId && viewId ? VIEW_DETAILS(viewId as string) : null, + workspaceSlug && projectId && viewId + ? () => + viewsService.getViewDetails( + workspaceSlug as string, + projectId as string, + viewId as string + ) + : null + ); + const setIssueViewToKanban = useCallback(() => { dispatch({ type: "SET_ISSUE_VIEW", @@ -335,15 +363,33 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = }; }, false); - saveDataToServer(workspaceSlug as string, projectId as string, { - ...state, - filters: { - ...state.filters, - ...property, - }, - }); + if (viewId) { + mutateViewDetails((prevData: any) => { + if (!prevData) return prevData; + return { + ...prevData, + query_data: { + ...state.filters, + ...property, + }, + }; + }, false); + sendFilterDataToServer(workspaceSlug as string, projectId as string, viewId as string, { + query_data: { + ...state.filters, + ...property, + }, + }); + } else + saveDataToServer(workspaceSlug as string, projectId as string, { + ...state, + filters: { + ...state.filters, + ...property, + }, + }); }, - [projectId, workspaceSlug, state, mutateMyViewProps] + [projectId, workspaceSlug, state, mutateMyViewProps, viewId, mutateViewDetails] ); const setNewDefaultView = useCallback(() => { @@ -368,9 +414,15 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = useEffect(() => { dispatch({ type: "REHYDRATE_THEME", - payload: myViewProps?.view_props, + payload: { + ...myViewProps?.view_props, + filters: { + ...myViewProps?.view_props?.filters, + ...viewDetails?.query_data, + } as any, + }, }); - }, [myViewProps]); + }, [myViewProps, viewDetails]); useEffect(() => { // TODO: think of a better way to do this @@ -380,11 +432,14 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = } else if (moduleId) { mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string), {}, false); mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string)); + } else if (viewId) { + mutate(VIEW_ISSUES(viewId as string), {}, false); + mutate(VIEW_ISSUES(viewId as string)); } else { mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string), {}, false); mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string)); } - }, [state, projectId, cycleId, moduleId]); + }, [state, projectId, cycleId, moduleId, viewId]); return ( { const { @@ -36,18 +38,22 @@ const useIssuesView = () => { } = useContext(issueViewContext); const router = useRouter(); - const { workspaceSlug, projectId, cycleId, moduleId } = router.query; + const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; const params: any = { order_by: orderBy, group_by: groupByProperty, - assignees: filters.assignees ? filters.assignees.join(",") : undefined, - type: filters.type ? filters.type : undefined, - labels: filters.labels ? filters.labels.join(",") : undefined, - issue__assignees__id: filters.issue__assignees__id - ? filters.issue__assignees__id.join(",") + assignees: filters?.assignees ? filters?.assignees.join(",") : undefined, + state: filters?.state ? filters?.state.join(",") : undefined, + priority: filters?.priority ? filters?.priority.join(",") : undefined, + type: filters?.type ? filters?.type : undefined, + labels: filters?.labels ? filters?.labels.join(",") : undefined, + issue__assignees__id: filters?.issue__assignees__id + ? filters?.issue__assignees__id.join(",") + : undefined, + issue__labels__id: filters?.issue__labels__id + ? filters?.issue__labels__id.join(",") : undefined, - issue__labels__id: filters.issue__labels__id ? filters.issue__labels__id.join(",") : undefined, }; const { data: projectIssues } = useSWR( @@ -60,6 +66,14 @@ const useIssuesView = () => { : null ); + const { data: viewIssues } = useSWR( + workspaceSlug && projectId && viewId ? VIEW_ISSUES(viewId as string) : null, + workspaceSlug && projectId && viewId + ? () => + viewsService.getViewIssues(workspaceSlug as string, projectId as string, viewId as string) + : null + ); + const { data: cycleIssues } = useSWR( workspaceSlug && projectId && cycleId && params ? CYCLE_ISSUES_WITH_PARAMS(cycleId as string) @@ -95,11 +109,11 @@ const useIssuesView = () => { [key: string]: IIssue[]; } | undefined = useMemo(() => { - const issuesToGroup = cycleIssues ?? moduleIssues ?? projectIssues; + const issuesToGroup = viewIssues ?? cycleIssues ?? moduleIssues ?? projectIssues; if (Array.isArray(issuesToGroup)) return { allIssues: issuesToGroup }; else return issuesToGroup; - }, [projectIssues, cycleIssues, moduleIssues]); + }, [projectIssues, cycleIssues, moduleIssues, viewIssues]); return { groupedByIssues, diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/[viewId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/[viewId].tsx index a3ab9b22b..df01b2129 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/[viewId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/[viewId].tsx @@ -10,6 +10,8 @@ import projectService from "services/project.service"; import viewsService from "services/views.service"; // layouts import AppLayout from "layouts/app-layout"; +// contexts +import { IssueViewContextProvider } from "contexts/issue-view.context"; // components import { CreateUpdateViewModal, DeleteViewModal } from "components/views"; // ui @@ -18,6 +20,9 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { UserAuth } from "types"; // fetch-keys import { PROJECT_DETAILS, VIEW_DETAILS, VIEW_ISSUES } from "constants/fetch-keys"; +import { IssuesFilterView, IssuesView } from "components/core"; +import { HeaderButton } from "components/ui"; +import { PlusIcon } from "@heroicons/react/24/outline"; const SingleView: React.FC = (props) => { const router = useRouter(); @@ -42,27 +47,36 @@ const SingleView: React.FC = (props) => { : null ); - const { data: viewIssues } = useSWR( - workspaceSlug && projectId && viewId ? VIEW_ISSUES(viewId as string) : null, - workspaceSlug && projectId && viewId - ? () => - viewsService.getViewIssues(workspaceSlug as string, projectId as string, viewId as string) - : null - ); - return ( - - - - } - > - Content here - + + + + + } + right={ +
+ + { + const e = new KeyboardEvent("keydown", { + key: "c", + }); + document.dispatchEvent(e); + }} + /> +
+ } + > + +
+
); }; diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx new file mode 100644 index 000000000..b755d5bef --- /dev/null +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/views/index.tsx @@ -0,0 +1,124 @@ +import React, { useState } from "react"; + +import Link from "next/link"; +import { useRouter } from "next/router"; + +import useSWR from "swr"; + +// lib +import { requiredAuth } from "lib/auth"; + +// services +import viewsService from "services/views.service"; +import projectService from "services/project.service"; + +// layouts +import AppLayout from "layouts/app-layout"; +// ui +import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; +// icons +import { TrashIcon } from "@heroicons/react/20/solid"; +// fetching keys +import { PROJECT_DETAILS, VIEWS_LIST } from "constants/fetch-keys"; +// components +import { CustomMenu, Spinner } from "components/ui"; +import { DeleteViewModal } from "components/views"; +// types +import { IView } from "types"; +import type { NextPage, GetServerSidePropsContext } from "next"; + +const ProjectViews: NextPage = () => { + const [selectedView, setSelectedView] = useState(null); + + const { + query: { workspaceSlug, projectId }, + } = useRouter(); + + const { data: activeProject } = useSWR( + workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, + workspaceSlug && projectId + ? () => projectService.getProject(workspaceSlug as string, projectId as string) + : null + ); + + const { data: views } = useSWR( + workspaceSlug && projectId ? VIEWS_LIST(projectId as string) : null, + workspaceSlug && projectId + ? () => viewsService.getViews(workspaceSlug as string, projectId as string) + : null + ); + + return ( + + + + + } + > + setSelectedView(null)} + onSuccess={() => setSelectedView(null)} + /> +
+ {views ? ( + views.map((view) => ( +
+ + {view.name} + + + { + setSelectedView(view); + }} + > + + + Delete + + + +
+ )) + ) : ( +
+ +
+ )} +
+
+ ); +}; + +export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { + const user = await requiredAuth(ctx.req?.headers.cookie); + + const redirectAfterSignIn = ctx.resolvedUrl; + + if (!user) { + return { + redirect: { + destination: `/signin?next=${redirectAfterSignIn}`, + permanent: false, + }, + }; + } + + return { + props: { + user, + }, + }; +}; + +export default ProjectViews; diff --git a/apps/app/services/views.service.ts b/apps/app/services/views.service.ts index 77b2e82e8..a3bdf550c 100644 --- a/apps/app/services/views.service.ts +++ b/apps/app/services/views.service.ts @@ -47,8 +47,8 @@ class ViewServices extends APIService { }); } - async deleteView(projectId: string, viewId: string): Promise { - return this.delete(`/api/projects/${projectId}/views/${viewId}/`) + async deleteView(workspaceSlug: string, projectId: string, viewId: string): Promise { + return this.delete(`/api/workspaces/${workspaceSlug}/projects/${projectId}/views/${viewId}/`) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; diff --git a/apps/app/types/issues.d.ts b/apps/app/types/issues.d.ts index 42d3cc84f..91f56768e 100644 --- a/apps/app/types/issues.d.ts +++ b/apps/app/types/issues.d.ts @@ -210,9 +210,11 @@ export interface IIssueActivity { export interface IIssueFilterOptions { type: "active" | "backlog" | null; assignees: string[] | null; + state: string[] | null; labels: string[] | null; issue__assignees__id: string[] | null; issue__labels__id: string[] | null; + priority: string[] | null; } export interface IIssueViewOptions { diff --git a/apps/app/types/views.d.ts b/apps/app/types/views.d.ts index 2c7a3c89f..3e7f9f1d5 100644 --- a/apps/app/types/views.d.ts +++ b/apps/app/types/views.d.ts @@ -7,29 +7,32 @@ export interface IView { updated_by: string; name: string; description: string; - query: { - state: string[] | null; - parent: string[] | null; - labels: string[] | null; - assignees: string[] | null; - created_by: string[] | null; - name: string | null; - created_at: [ - { - datetime: string; - timeline: "before"; - }, - { - datetime: string; - timeline: "after"; - } - ]; - updated_at: string[] | null; - start_date: string[] | null; - target_date: string[] | null; - completed_at: string[] | null; - type: string; - }; + query: IQuery; + query_data: IQuery; project: string; workspace: string; } + +export interface IQuery { + state: string[] | null; + parent: string[] | null; + labels: string[] | null; + assignees: string[] | null; + created_by: string[] | null; + name: string | null; + created_at: [ + { + datetime: string; + timeline: "before"; + }, + { + datetime: string; + timeline: "after"; + } + ]; + updated_at: string[] | null; + start_date: string[] | null; + target_date: string[] | null; + completed_at: string[] | null; + type: string; +}