diff --git a/packages/types/src/index.d.ts b/packages/types/src/index.d.ts index 591cf30c2..3181e6653 100644 --- a/packages/types/src/index.d.ts +++ b/packages/types/src/index.d.ts @@ -27,7 +27,7 @@ export * from "./auth"; export * from "./api_token"; export * from "./instance"; export * from "./app"; -export * from "./view"; +export * from "./view/root"; export type NestedKeyOf = { [Key in keyof ObjectType & (string | number)]: ObjectType[Key] extends object diff --git a/packages/types/src/view.d.ts b/packages/types/src/view.d.ts deleted file mode 100644 index 94fd66581..000000000 --- a/packages/types/src/view.d.ts +++ /dev/null @@ -1,88 +0,0 @@ -declare enum EGlobalViewAccess { - "public" = 0, - "private" = 1, - "shared" = 2, -} - -export type TViewAccess = - | EGlobalViewAccess.public - | EGlobalViewAccess.private - | EGlobalViewAccess.shared; - -export type TViewLayouts = - | "list" - | "kanban" - | "calendar" - | "spreadsheet" - | "gantt"; - -export type TViewFilters = { - project: string[]; - priority: string[]; - state: string[]; - state_group: string[]; - assignees: string[]; - mentions: string[]; - created_by: string[]; - label: string[]; - start_date: string[]; - target_date: string[]; -}; - -export type TViewDisplayFilters = { - group_by: string; - sub_group_by: string; - order_by: string; - issue_type: string; - layout: TViewLayouts; -}; - -export type TViewDisplayProperties = { - assignee: boolean; - start_date: boolean; - due_date: boolean; - labels: boolean; - key: boolean; - priority: boolean; - state: boolean; - sub_issue_count: boolean; - link: boolean; - attachment_count: boolean; - estimate: boolean; - created_on: boolean; - updated_on: boolean; -}; - -export type TViewFilterProps = { - filters: TViewFilters; - display_filters: TViewDisplayFilters; - display_properties: TViewDisplayProperties; -}; - -export type TViewFilterPartialProps = { - filters: Partial; - display_filters: Partial; - display_properties: Partial; -}; - -export type TView = { - readonly id: string; - readonly workspace: string; - readonly project: string | undefined; - name: string; - description: string; - readonly query: string; - filters: TViewFilters; - display_filters: TViewDisplayFilters; - display_properties: TViewDisplayProperties; - readonly access: TViewAccess; - readonly owned_by: string; - readonly sort_order: number; - readonly is_locked: boolean; - readonly is_pinned: boolean; - readonly is_favorite: boolean; - readonly created_by: string; - readonly updated_by: string; - readonly created_at: Date; - readonly updated_at: Date; -}; diff --git a/packages/types/src/view/base.d.ts b/packages/types/src/view/base.d.ts new file mode 100644 index 000000000..5108f891c --- /dev/null +++ b/packages/types/src/view/base.d.ts @@ -0,0 +1,34 @@ +import { TFilters, TDisplayFilters, TDisplayProperties } from "./filter"; + +declare enum EGlobalViewAccess { + "public" = 0, + "private" = 1, + "shared" = 2, +} + +export type TViewAccess = + | EGlobalViewAccess.public + | EGlobalViewAccess.private + | EGlobalViewAccess.shared; + +export type TView = { + readonly id: string; + readonly workspace: string; + readonly project: string | undefined; + name: string; + description: string; + readonly query: string; + filters: TFilters; + display_filters: TDisplayFilters; + display_properties: TDisplayProperties; + readonly access: TViewAccess; + readonly owned_by: string; + readonly sort_order: number; + readonly is_locked: boolean; + readonly is_pinned: boolean; + readonly is_favorite: boolean; + readonly created_by: string; + readonly updated_by: string; + readonly created_at: Date; + readonly updated_at: Date; +}; diff --git a/packages/types/src/view/filter.d.ts b/packages/types/src/view/filter.d.ts new file mode 100644 index 000000000..6c2389139 --- /dev/null +++ b/packages/types/src/view/filter.d.ts @@ -0,0 +1,74 @@ +export type TLayouts = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt"; + +export type TCalendarLayouts = "month" | "week"; + +export type TFilters = { + project: string[]; + priority: string[]; + state: string[]; + state_group: string[]; + assignees: string[]; + mentions: string[]; + subscriber: string[]; + created_by: string[]; + labels: string[]; + start_date: string[]; + target_date: string[]; +}; + +export type TDisplayFilters = { + layout: TLayouts; + group_by: string | undefined; + sub_group_by: string | undefined; + order_by: string; + type: string | undefined; + sub_issue: boolean; + show_empty_groups: boolean; + calendar: { + show_weekends: boolean; + layout: TCalendarLayouts; + }; +}; + +export type TDisplayProperties = { + assignee: boolean; + start_date: boolean; + due_date: boolean; + labels: boolean; + key: boolean; + priority: boolean; + state: boolean; + sub_issue_count: boolean; + link: boolean; + attachment_count: boolean; + estimate: boolean; + created_on: boolean; + updated_on: boolean; +}; + +export type TFilterProps = { + filters: TFilters; + display_filters: TDisplayFilters; + display_properties: TDisplayProperties; +}; + +export type TFilterPartialProps = { + filters: Partial; + display_filters: Partial; + display_properties: Partial; +}; + +export type TFilterQueryParams = + | "project" + | "priority" + | "state" + | "state_group" + | "assignees" + | "mentions" + | "subscriber" + | "created_by" + | "labels" + | "start_date" + | "target_date" + | "type" + | "sub_issue"; diff --git a/packages/types/src/view/root.d.ts b/packages/types/src/view/root.d.ts new file mode 100644 index 000000000..3555fb52c --- /dev/null +++ b/packages/types/src/view/root.d.ts @@ -0,0 +1,2 @@ +export * from "./filter"; +export * from "./base"; diff --git a/web/store/view/filters_helpers.ts b/web/store/view/filters_helpers.ts index 642f2329e..f2ade24f6 100644 --- a/web/store/view/filters_helpers.ts +++ b/web/store/view/filters_helpers.ts @@ -1 +1,104 @@ -export class ViewFilterHelpers {} +import isEmpty from "lodash/isEmpty"; +// types +import { TFilters, TDisplayFilters, TDisplayProperties, TFilterProps, TFilterQueryParams } from "@plane/types"; + +export class FiltersHelper { + // computed filters + computedFilters = (filters: TFilters, defaultValues?: Partial): TFilters => ({ + project: filters?.project || defaultValues?.project || [], + priority: filters?.priority || defaultValues?.priority || [], + state: filters?.state || defaultValues?.state || [], + state_group: filters?.state_group || defaultValues?.state_group || [], + assignees: filters?.assignees || defaultValues?.assignees || [], + mentions: filters?.mentions || defaultValues?.mentions || [], + subscriber: filters?.subscriber || defaultValues?.subscriber || [], + created_by: filters?.created_by || defaultValues?.created_by || [], + labels: filters?.labels || defaultValues?.labels || [], + start_date: filters?.start_date || defaultValues?.start_date || [], + target_date: filters?.target_date || defaultValues?.target_date || [], + }); + + // computed display filters + computedDisplayFilters = ( + displayFilters: TDisplayFilters, + defaultValues?: Partial + ): TDisplayFilters => ({ + layout: displayFilters?.layout || defaultValues?.layout || "list", + group_by: displayFilters?.group_by || defaultValues?.group_by || "none", + sub_group_by: displayFilters?.sub_group_by || defaultValues?.sub_group_by || undefined, + order_by: displayFilters?.order_by || defaultValues?.order_by || "sort_order", + type: displayFilters?.type || defaultValues?.type || undefined, + sub_issue: displayFilters?.sub_issue || defaultValues?.sub_issue || false, + show_empty_groups: displayFilters?.show_empty_groups || defaultValues?.show_empty_groups || false, + calendar: { + show_weekends: displayFilters?.calendar?.show_weekends || defaultValues?.calendar?.show_weekends || false, + layout: displayFilters?.calendar?.layout || defaultValues?.calendar?.layout || "month", + }, + }); + + // computed display properties + computedDisplayProperties = ( + displayProperties: TDisplayProperties, + defaultValues?: Partial + ): TDisplayProperties => ({ + assignee: displayProperties?.assignee || defaultValues?.assignee || true, + start_date: displayProperties?.start_date || defaultValues?.start_date || true, + due_date: displayProperties?.due_date || defaultValues?.due_date || true, + labels: displayProperties?.labels || defaultValues?.labels || true, + priority: displayProperties?.priority || defaultValues?.priority || true, + state: displayProperties?.state || defaultValues?.state || true, + sub_issue_count: displayProperties?.sub_issue_count || defaultValues?.sub_issue_count || true, + attachment_count: displayProperties?.attachment_count || defaultValues?.attachment_count || true, + link: displayProperties?.link || defaultValues?.link || true, + estimate: displayProperties?.estimate || defaultValues?.estimate || true, + key: displayProperties?.key || defaultValues?.key || true, + created_on: displayProperties?.created_on || defaultValues?.created_on || true, + updated_on: displayProperties?.updated_on || defaultValues?.updated_on || true, + }); + + // compute filters and display_filters issue query parameters + computeAppliedFiltersQueryParameters = ( + filters: TFilterProps, + acceptableParamsByLayout: string[] + ): { params: any; query: string } => { + const paramsObject: Partial> = {}; + let paramsString = ""; + + const filteredParams: Partial> = { + // issue filters + priority: filters.filters?.priority || undefined, + state_group: filters.filters?.state_group || undefined, + state: filters.filters?.state || undefined, + assignees: filters.filters?.assignees || undefined, + mentions: filters.filters?.mentions || undefined, + created_by: filters.filters?.created_by || undefined, + labels: filters.filters?.labels || undefined, + start_date: filters.filters?.start_date || undefined, + target_date: filters.filters?.target_date || undefined, + project: filters.filters?.project || undefined, + subscriber: filters.filters?.subscriber || undefined, + // display filters + type: filters?.display_filters?.type || undefined, + sub_issue: filters?.display_filters?.sub_issue || true, + }; + + Object.keys(filteredParams).forEach((key) => { + const _key = key as TFilterQueryParams; + const _value: string | boolean | string[] | undefined = filteredParams[_key]; + if (_value != undefined && acceptableParamsByLayout.includes(_key)) + paramsObject[_key] = Array.isArray(_value) ? _value.join(",") : _value; + }); + + if (paramsObject && !isEmpty(paramsObject)) { + paramsString = Object.keys(paramsObject) + .map((key) => { + const _key = key as TFilterQueryParams; + const _value: string | boolean | undefined = paramsObject[_key]; + if (!undefined) return `${_key}=${_value}`; + }) + .join("&"); + } + + return { params: paramsObject, query: paramsString }; + }; +} diff --git a/web/store/view/view-root.store.ts b/web/store/view/view-root.store.ts index 81057fce1..e692a37f9 100644 --- a/web/store/view/view-root.store.ts +++ b/web/store/view/view-root.store.ts @@ -18,7 +18,7 @@ type TViewRoot = { // actions fetch: () => Promise; create: (view: Partial) => Promise; - delete: (viewId: string) => Promise; + remove: (viewId: string) => Promise; duplicate: (viewId: string) => Promise; }; @@ -34,7 +34,7 @@ export class ViewRoot implements TViewRoot { // actions fetch: action, create: action, - delete: action, + remove: action, duplicate: action, }); } @@ -49,6 +49,10 @@ export class ViewRoot implements TViewRoot { } // actions + /** + * @description This method is used to fetch all the views + * @returns + */ fetch = async () => { const { workspaceSlug, projectId } = this.store.app.router; if (!workspaceSlug) return; @@ -63,6 +67,11 @@ export class ViewRoot implements TViewRoot { }); }; + /** + * @description This method is used to create a view + * @param data: Partial + * @returns + */ create = async (data: Partial) => { const { workspaceSlug, projectId } = this.store.app.router; if (!workspaceSlug) return; @@ -75,7 +84,12 @@ export class ViewRoot implements TViewRoot { }); }; - delete = async (viewId: string) => { + /** + * @description This method is used to remove a view + * @param viewId: string + * @returns + */ + remove = async (viewId: string) => { const { workspaceSlug, projectId } = this.store.app.router; if (!workspaceSlug) return; @@ -86,6 +100,11 @@ export class ViewRoot implements TViewRoot { }); }; + /** + * @description This method is used to duplicate a view + * @param viewId: string + * @returns + */ duplicate = async (viewId: string) => { const { workspaceSlug, projectId } = this.store.app.router; if (!workspaceSlug) return; diff --git a/web/store/view/view.store.ts b/web/store/view/view.store.ts index c482f224b..7341be21e 100644 --- a/web/store/view/view.store.ts +++ b/web/store/view/view.store.ts @@ -6,27 +6,29 @@ import { RootStore } from "store/root.store"; import { TViewService } from "services/view/types"; import { TView, - TViewFilters, - TViewDisplayFilters, - TViewDisplayProperties, - TViewFilterProps, - TViewFilterPartialProps, + TFilters, + TDisplayFilters, + TDisplayProperties, + TFilterProps, + TFilterPartialProps, TViewAccess, } from "@plane/types"; +// helpers +import { FiltersHelper } from "./filters_helpers"; type TLoader = "submitting" | "submit" | undefined; export type TViewsStore = TView & { // observables loader: TLoader; - filtersToUpdate: TViewFilterPartialProps; + filtersToUpdate: TFilterPartialProps; // computed - appliedFilters: TViewFilterProps | undefined; - appliedFiltersQueryParams: undefined; + appliedFilters: TFilterProps | undefined; + appliedFiltersQueryParams: string | undefined; // helper actions - updateFilters: (filters: Partial) => void; - updateDisplayFilters: (display_filters: Partial) => void; - updateDisplayProperties: (display_properties: Partial) => void; + updateFilters: (filters: Partial) => void; + updateDisplayFilters: (display_filters: Partial) => void; + updateDisplayProperties: (display_properties: Partial) => void; resetFilterChanges: () => void; saveFilterChanges: () => void; // actions @@ -37,16 +39,16 @@ export type TViewsStore = TView & { update: (viewData: Partial) => Promise; }; -export class ViewsStore implements TViewsStore { +export class ViewsStore extends FiltersHelper implements TViewsStore { id: string; workspace: string; project: string | undefined; name: string; description: string; query: string; - filters: TViewFilters; - display_filters: TViewDisplayFilters; - display_properties: TViewDisplayProperties; + filters: TFilters; + display_filters: TDisplayFilters; + display_properties: TDisplayProperties; access: TViewAccess; owned_by: string; sort_order: number; @@ -59,22 +61,23 @@ export class ViewsStore implements TViewsStore { updated_at: Date; loader: TLoader = undefined; - filtersToUpdate: TViewFilterPartialProps = { + filtersToUpdate: TFilterPartialProps = { filters: {}, display_filters: {}, display_properties: {}, }; constructor(private store: RootStore, _view: TView, private service: TViewService) { + super(); this.id = _view.id; this.workspace = _view.workspace; this.project = _view.project; this.name = _view.name; this.description = _view.description; this.query = _view.query; - this.filters = _view.filters; - this.display_filters = _view.display_filters; - this.display_properties = _view.display_properties; + this.filters = this.computedFilters(_view.filters); + this.display_filters = this.computedDisplayFilters(_view.display_filters); + this.display_properties = this.computedDisplayProperties(_view.display_properties); this.access = _view.access; this.owned_by = _view.owned_by; this.sort_order = _view.sort_order; @@ -108,35 +111,74 @@ export class ViewsStore implements TViewsStore { // computed get appliedFilters() { - return undefined; + return { + filters: this.computedFilters(this.filters, this.filtersToUpdate.filters), + display_filters: this.computedDisplayFilters(this.display_filters, this.filtersToUpdate.display_filters), + display_properties: this.computedDisplayProperties( + this.display_properties, + this.filtersToUpdate.display_properties + ), + }; } get appliedFiltersQueryParams() { - return undefined; + const filters = this.appliedFilters; + return this.computeAppliedFiltersQueryParameters(filters, [])?.query || undefined; } // helper actions - updateFilters = (filters: Partial) => { + /** + * @description This method is used to update the filters of the view + * @param filters: Partial + */ + updateFilters = (filters: Partial) => { runInAction(() => { this.loader = "submit"; this.filtersToUpdate.filters = filters; }); }; - updateDisplayFilters = async (display_filters: Partial) => { + /** + * @description This method is used to update the display filters of the view + * @param display_filters: Partial + */ + updateDisplayFilters = async (display_filters: Partial) => { + const appliedFilters = this.appliedFilters; + + const layout = appliedFilters.display_filters.layout; + const sub_group_by = appliedFilters.display_filters.sub_group_by; + const group_by = appliedFilters.display_filters.group_by; + const sub_issue = appliedFilters.display_filters.sub_issue; + + if (group_by === undefined) display_filters.sub_group_by = undefined; + + if (layout === "kanban") { + if (sub_group_by === group_by) display_filters.group_by = undefined; + if (group_by === null) display_filters.group_by = "state"; + } + + if (layout === "spreadsheet" && sub_issue === true) display_filters.sub_issue = false; + runInAction(() => { this.loader = "submit"; this.filtersToUpdate.display_filters = display_filters; }); }; - updateDisplayProperties = async (display_properties: Partial) => { + /** + * @description This method is used to update the display properties of the view + * @param display_properties: Partial + */ + updateDisplayProperties = async (display_properties: Partial) => { runInAction(() => { this.loader = "submit"; this.filtersToUpdate.display_properties = display_properties; }); }; + /** + * @description This method is used to reset the changes made to the filters + */ resetFilterChanges = () => { runInAction(() => { this.loader = undefined; @@ -148,6 +190,9 @@ export class ViewsStore implements TViewsStore { }); }; + /** + * @description This method is used to save the changes made to the filters + */ saveFilterChanges = async () => { this.loader = "submitting"; if (this.appliedFilters) await this.update(this.appliedFilters); @@ -155,6 +200,10 @@ export class ViewsStore implements TViewsStore { }; // actions + /** + * @description This method is used to update the view lock + * @returns + */ lockView = async () => { const { workspaceSlug, projectId } = this.store.app.router; if (!workspaceSlug) return; @@ -167,6 +216,10 @@ export class ViewsStore implements TViewsStore { }); }; + /** + * @description This method is used to remove the view lock + * @returns + */ unlockView = async () => { const { workspaceSlug, projectId } = this.store.app.router; if (!workspaceSlug) return; @@ -179,6 +232,10 @@ export class ViewsStore implements TViewsStore { }); }; + /** + * @description This method is used to update the view favorite + * @returns + */ makeFavorite = async () => { const { workspaceSlug, projectId } = this.store.app.router; if (!workspaceSlug) return; @@ -191,6 +248,10 @@ export class ViewsStore implements TViewsStore { }); }; + /** + * @description This method is used to remove the view favorite + * @returns + */ removeFavorite = async () => { const { workspaceSlug, projectId } = this.store.app.router; if (!workspaceSlug) return; @@ -203,22 +264,22 @@ export class ViewsStore implements TViewsStore { }); }; + /** + * @description This method is used to update the view + * @param viewData + */ update = async (viewData: Partial) => { - try { - const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug) return; + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug) return; - const view = await this.service.update(workspaceSlug, this.id, viewData, projectId); - if (!view) return; + const view = await this.service.update(workspaceSlug, this.id, viewData, projectId); + if (!view) return; - runInAction(() => { - Object.keys(viewData).forEach((key) => { - const _key = key as keyof TView; - set(this, _key, viewData[_key]); - }); + runInAction(() => { + Object.keys(viewData).forEach((key) => { + const _key = key as keyof TView; + set(this, _key, viewData[_key]); }); - } catch (error) { - console.log(error); - } + }); }; }