Compare commits

...

1 Commits

Author SHA1 Message Date
rahulramesha
32b4019a69 add Layout options while creating a new view 2024-06-19 16:32:31 +05:30
9 changed files with 79 additions and 26 deletions

View File

@@ -13,6 +13,9 @@ export interface IProjectView {
is_favorite: boolean;
created_by: string;
updated_by: string;
isLocked: boolean | null;
isPrivate: boolean | null;
isPublished: boolean | null;
name: string;
description: string;
filters: IIssueFilterOptions;

View File

@@ -31,7 +31,7 @@ export const DropdownButton: React.FC<IMultiSelectDropdownButton | ISingleSelect
)}
onClick={handleOnClick}
>
{buttonContent ? <>{buttonContent(isOpen)}</> : <span className={cn("", buttonClassName)}>{value}</span>}
{buttonContent ? <>{buttonContent(isOpen, value)}</> : <span className={cn("", buttonClassName)}>{value}</span>}
</button>
</Combobox.Button>
);

View File

@@ -22,6 +22,7 @@ export const DropdownOptions: React.FC<IMultiSelectDropdownOptions | ISingleSele
disableSearch,
keyExtractor,
options,
handleClose,
value,
renderItem,
loader,
@@ -46,7 +47,7 @@ export const DropdownOptions: React.FC<IMultiSelectDropdownOptions | ISingleSele
options?.map((option) => (
<Combobox.Option
key={keyExtractor(option)}
value={option.data[option.value]}
value={option.value}
className={({ active, selected }) =>
cn(
"flex w-full cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5",
@@ -58,6 +59,7 @@ export const DropdownOptions: React.FC<IMultiSelectDropdownOptions | ISingleSele
option.className && option.className({ active, selected })
)
}
onClick={handleClose}
>
{({ selected }) => (
<>
@@ -65,7 +67,7 @@ export const DropdownOptions: React.FC<IMultiSelectDropdownOptions | ISingleSele
<>{renderItem({ value: option.data[option.value], selected })}</>
) : (
<>
<span className="flex-grow truncate">{value}</span>
<span className="flex-grow truncate">{option.value}</span>
{selected && <Check className="h-3.5 w-3.5 flex-shrink-0" />}
</>
)}

View File

@@ -24,8 +24,8 @@ export interface IDropdown {
// options props
keyExtractor: (option: TDropdownOption) => string;
optionsContainerClassName?: string;
queryArray: string[];
sortByKey: string;
queryArray?: string[];
sortByKey?: string;
firstItem?: (optionValue: string) => boolean;
renderItem?: ({ value, selected }: { value: string; selected: boolean }) => React.ReactNode;
loader?: React.ReactNode;
@@ -52,7 +52,7 @@ export interface ISingleSelectDropdown extends IDropdown {
export interface IDropdownButton {
isOpen: boolean;
buttonContent?: (isOpen: boolean) => React.ReactNode;
buttonContent?: (isOpen: boolean, value: string | string[] | undefined) => React.ReactNode;
buttonClassName?: string;
buttonContainerClassName?: string;
handleOnClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
@@ -79,6 +79,8 @@ export interface IDropdownOptions {
inputContainerClassName?: string;
disableSearch?: boolean;
handleClose?: () => void;
keyExtractor: (option: TDropdownOption) => string;
renderItem: (({ value, selected }: { value: string; selected: boolean }) => React.ReactNode) | undefined;
options: TDropdownOption[] | undefined;

View File

@@ -90,10 +90,12 @@ export const MultiSelectDropdown: FC<IMultiSelectDropdown> = (props) => {
const sortedOptions = useMemo(() => {
if (!options) return undefined;
const filteredOptions = (options || []).filter((options) => {
const queryString = queryArray.map((query) => options.data[query]).join(" ");
return queryString.toLowerCase().includes(query.toLowerCase());
});
const filteredOptions = queryArray
? (options || []).filter((options) => {
const queryString = queryArray.map((query) => options.data[query]).join(" ");
return queryString.toLowerCase().includes(query.toLowerCase());
})
: options;
if (disableSorting) return filteredOptions;

View File

@@ -90,12 +90,14 @@ export const Dropdown: FC<ISingleSelectDropdown> = (props) => {
const sortedOptions = useMemo(() => {
if (!options) return undefined;
const filteredOptions = (options || []).filter((options) => {
const queryString = queryArray.map((query) => options.data[query]).join(" ");
return queryString.toLowerCase().includes(query.toLowerCase());
});
const filteredOptions = queryArray
? (options || []).filter((options) => {
const queryString = queryArray.map((query) => options.data[query]).join(" ");
return queryString.toLowerCase().includes(query.toLowerCase());
})
: options;
if (disableSorting) return filteredOptions;
if (disableSorting || !sortByKey) return filteredOptions;
return sortBy(filteredOptions, [
(option) => firstItem && firstItem(option.data[option.value]),
@@ -157,6 +159,7 @@ export const Dropdown: FC<ISingleSelectDropdown> = (props) => {
value={value}
renderItem={renderItem}
loader={loader}
handleClose={handleClose}
/>
</div>
</Combobox.Options>

View File

@@ -0,0 +1,26 @@
import { observer } from "mobx-react";
import { Dropdown } from "@plane/ui";
import { EIssueLayoutTypes, ISSUE_LAYOUT_MAP } from "@/constants/issue";
type TLayoutDropDown = {
onChange: (value: EIssueLayoutTypes) => void;
value: EIssueLayoutTypes;
};
export const LayoutDropDown = observer((props: TLayoutDropDown) => {
const { onChange, value = EIssueLayoutTypes.LIST } = props;
const options = Object.values(ISSUE_LAYOUT_MAP).map((issueLayout) => ({
data: issueLayout.key,
value: issueLayout.key,
}));
return (
<Dropdown
onChange={onChange as (value: string) => void}
value={value?.toString()}
keyExtractor={(option) => option.value}
options={options}
disableSearch
/>
);
});

View File

@@ -11,11 +11,12 @@ import { Button, EmojiIconPicker, EmojiIconPickerTypes, Input, PhotoFilterIcon,
import { Logo } from "@/components/common";
import { AppliedFiltersList, FilterSelection, FiltersDropdown } from "@/components/issues";
// constants
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
import { EIssueLayoutTypes, ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "@/constants/issue";
// helpers
import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper";
// hooks
import { useLabel, useMember, useProject, useProjectState } from "@/hooks/store";
import { LayoutDropDown } from "../dropdowns/layout";
type Props = {
data?: IProjectView | null;
@@ -48,7 +49,7 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
reset,
setValue,
watch,
} = useForm<IProjectView>({
} = useForm<IProjectView & { layout: EIssueLayoutTypes }>({
defaultValues,
});
@@ -90,13 +91,13 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
});
};
const handleCreateUpdateView = async (formData: IProjectView) => {
const handleCreateUpdateView = async (formData: IProjectView & { layout: EIssueLayoutTypes }) => {
await handleFormSubmit({
name: formData.name,
description: formData.description,
logo_props: formData.logo_props,
filters: formData.filters,
display_filters: formData.display_filters,
display_filters: { ...formData.display_filters, layout: formData.layout },
display_properties: formData.display_properties,
} as IProjectView);
@@ -211,6 +212,18 @@ export const ProjectViewForm: React.FC<Props> = observer((props) => {
)}
/>
</div>
<div>
<Controller
control={control}
name="layout"
render={({ field: { onChange, value } }) => (
<LayoutDropDown
onChange={(selectedValue: EIssueLayoutTypes) => onChange(selectedValue)}
value={value}
/>
)}
/>
</div>
<div>
<Controller
control={control}

View File

@@ -138,17 +138,19 @@ export const ISSUE_EXTRA_OPTIONS: {
{ key: "show_empty_groups", title: "Show empty groups" }, // filter on front-end
];
export const ISSUE_LAYOUT_MAP = {
[EIssueLayoutTypes.LIST]: { key: EIssueLayoutTypes.LIST, title: "List Layout", icon: List },
[EIssueLayoutTypes.KANBAN]: { key: EIssueLayoutTypes.KANBAN, title: "Kanban Layout", icon: Kanban },
[EIssueLayoutTypes.CALENDAR]: { key: EIssueLayoutTypes.CALENDAR, title: "Calendar Layout", icon: Calendar },
[EIssueLayoutTypes.SPREADSHEET]: { key: EIssueLayoutTypes.SPREADSHEET, title: "Spreadsheet Layout", icon: Sheet },
[EIssueLayoutTypes.GANTT]: { key: EIssueLayoutTypes.GANTT, title: "Gantt Chart Layout", icon: GanttChartSquare },
};
export const ISSUE_LAYOUTS: {
key: EIssueLayoutTypes;
title: string;
icon: any;
}[] = [
{ key: EIssueLayoutTypes.LIST, title: "List Layout", icon: List },
{ key: EIssueLayoutTypes.KANBAN, title: "Kanban Layout", icon: Kanban },
{ key: EIssueLayoutTypes.CALENDAR, title: "Calendar Layout", icon: Calendar },
{ key: EIssueLayoutTypes.SPREADSHEET, title: "Spreadsheet Layout", icon: Sheet },
{ key: EIssueLayoutTypes.GANTT, title: "Gantt Chart Layout", icon: GanttChartSquare },
];
}[] = Object.values(ISSUE_LAYOUT_MAP);
export interface ILayoutDisplayFiltersOptions {
filters: (keyof IIssueFilterOptions)[];