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

chore: due date filter #965

Merged
merged 21 commits into from
Jul 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0e12b38
chore: due date filter
kunalv17 Apr 28, 2023
9737722
fix: deployment error
kunalv17 Apr 28, 2023
d1a04ca
chore: optimized code
kunalv17 Apr 28, 2023
b2eade2
chore: created constants for due date
kunalv17 Apr 28, 2023
8214821
chore: create seperated css file for react datepicker styling
kunalv17 Apr 30, 2023
4c5dc28
fix: merge conflicts
aaryan610 May 26, 2023
84b54f7
fix: due date filter
aaryan610 May 26, 2023
59a4517
Merge branch 'hot-fix' of https://github.com/makeplane/plane into cho…
aaryan610 May 28, 2023
6cb2d88
chore: highlight selected option
aaryan610 May 28, 2023
b16ff47
Merge branch 'develop' of https://github.com/makeplane/plane into cho…
aaryan610 May 29, 2023
6234eee
Merge branch 'develop' of https://github.com/makeplane/plane into cho…
aaryan610 May 29, 2023
7afe671
fix: merge conflicts
aaryan610 Jun 29, 2023
e7e53f9
fix: merge conflicts
aaryan610 Jun 29, 2023
c89ceb3
fix: build error
aaryan610 Jun 29, 2023
d0a9918
Merge branch 'develop' of https://github.com/makeplane/plane into cho…
aaryan610 Jun 30, 2023
b5107e5
chore: date range selector validation
aaryan610 Jun 30, 2023
66d893e
fix: issue views overflow
aaryan610 Jul 4, 2023
11bcf66
refactor: due date filter modal code
aaryan610 Jul 4, 2023
bd3b821
Merge branch 'develop' of https://github.com/makeplane/plane into cho…
aaryan610 Jul 4, 2023
210296d
refactor: multi level dropdown
aaryan610 Jul 4, 2023
816b9e4
chore: due date filter select default value
aaryan610 Jul 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/app/components/core/calendar-view/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export const CalendarView: React.FC<Props> = ({
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || isCompleted;

return calendarIssues ? (
<div className="h-full">
<div className="h-full overflow-y-auto">
<DragDropContext onDragEnd={onDragEnd}>
<div className="h-full rounded-lg p-8 text-brand-secondary">
<CalendarHeader
Expand Down
186 changes: 186 additions & 0 deletions apps/app/components/core/filters/due-date-filter-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { Fragment } from "react";

import { useRouter } from "next/router";

// react-hook-form
import { Controller, useForm } from "react-hook-form";
// react-datepicker
import DatePicker from "react-datepicker";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// hooks
import useIssuesView from "hooks/use-issues-view";
// components
import { DueDateFilterSelect } from "./due-date-filter-select";
// ui
import { PrimaryButton, SecondaryButton } from "components/ui";
// icons
import { XMarkIcon } from "@heroicons/react/20/solid";
// helpers
import { renderDateFormat, renderShortDateWithYearFormat } from "helpers/date-time.helper";

type Props = {
isOpen: boolean;
handleClose: () => void;
};

type TFormValues = {
filterType: "before" | "after" | "range";
date1: Date;
date2: Date;
};

const defaultValues: TFormValues = {
filterType: "range",
date1: new Date(),
date2: new Date(new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()),
};

export const DueDateFilterModal: React.FC<Props> = ({ isOpen, handleClose }) => {
const { filters, setFilters } = useIssuesView();

const router = useRouter();
const { viewId } = router.query;

const { handleSubmit, watch, control } = useForm<TFormValues>({
defaultValues,
});

const handleFormSubmit = (formData: TFormValues) => {
const { filterType, date1, date2 } = formData;

if (filterType === "range") {
setFilters(
{ target_date: [`${renderDateFormat(date1)};after`, `${renderDateFormat(date2)};before`] },
!Boolean(viewId)
);
} else {
const filteredArray = filters?.target_date?.filter((item) => {
if (item?.includes(filterType)) return false;

return true;
});

const filterOne = filteredArray && filteredArray?.length > 0 ? filteredArray[0] : null;
if (filterOne)
setFilters(
{ target_date: [filterOne, `${renderDateFormat(date1)};${filterType}`] },
!Boolean(viewId)
);
else
setFilters(
{
target_date: [`${renderDateFormat(date1)};${filterType}`],
},
!Boolean(viewId)
);
}
handleClose();
};

const isInvalid =
watch("filterType") === "range" ? new Date(watch("date1")) > new Date(watch("date2")) : false;

const nextDay = new Date(watch("date1"));
nextDay.setDate(nextDay.getDate() + 1);

return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-brand-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 flex w-full justify-center overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative flex transform rounded-lg border border-brand-base bg-brand-base px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form className="space-y-4" onSubmit={handleSubmit(handleFormSubmit)}>
<div className="flex w-full justify-between">
<Controller
control={control}
name="filterType"
render={({ field: { value, onChange } }) => (
<DueDateFilterSelect value={value} onChange={onChange} />
)}
/>
<XMarkIcon
className="border-base h-4 w-4 cursor-pointer"
onClick={handleClose}
/>
</div>
<div className="flex w-full justify-between gap-4">
<Controller
control={control}
name="date1"
render={({ field: { value, onChange } }) => (
<DatePicker
selected={value}
onChange={(val) => onChange(val)}
dateFormat="dd-MM-yyyy"
calendarClassName="h-full"
inline
/>
)}
/>
{watch("filterType") === "range" && (
<Controller
control={control}
name="date2"
render={({ field: { value, onChange } }) => (
<DatePicker
selected={value}
onChange={onChange}
dateFormat="dd-MM-yyyy"
calendarClassName="h-full"
minDate={nextDay}
inline
/>
)}
/>
)}
</div>
{watch("filterType") === "range" && (
<h6 className="text-xs flex items-center gap-1">
<span className="text-brand-secondary">After:</span>
<span>{renderShortDateWithYearFormat(watch("date1"))}</span>
<span className="text-brand-secondary ml-1">Before:</span>
{!isInvalid && <span>{renderShortDateWithYearFormat(watch("date2"))}</span>}
</h6>
)}
<div className="flex justify-end gap-4">
<SecondaryButton className="flex items-center gap-2" onClick={handleClose}>
Cancel
</SecondaryButton>
<PrimaryButton
type="submit"
className="flex items-center gap-2"
disabled={isInvalid}
>
Apply
</PrimaryButton>
</div>
</form>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
};
58 changes: 58 additions & 0 deletions apps/app/components/core/filters/due-date-filter-select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from "react";

// ui
import { CustomSelect } from "components/ui";
// icons
import { CalendarBeforeIcon, CalendarAfterIcon, CalendarMonthIcon } from "components/icons";
// fetch-keys

type Props = {
value: string;
onChange: (value: string) => void;
};

type DueDate = {
name: string;
value: string;
icon: any;
};

const dueDateRange: DueDate[] = [
{
name: "Due date before",
value: "before",
icon: <CalendarBeforeIcon className="h-4 w-4 " />,
},
{
name: "Due date after",
value: "after",
icon: <CalendarAfterIcon className="h-4 w-4 " />,
},
{
name: "Due date range",
value: "range",
icon: <CalendarMonthIcon className="h-4 w-4 " />,
},
];

export const DueDateFilterSelect: React.FC<Props> = ({ value, onChange }) => (
<CustomSelect
value={value}
label={
<div className="flex items-center gap-2 text-xs">
{dueDateRange.find((item) => item.value === value)?.icon}
<span>{dueDateRange.find((item) => item.value === value)?.name}</span>
</div>
}
onChange={onChange}
>
{dueDateRange.map((option, index) => (
<CustomSelect.Option key={index} value={option.value}>
<>
<span>{option.icon}</span>
{option.name}
</>
</CustomSelect.Option>
))}
</CustomSelect>
);
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import stateService from "services/state.service";
// types
import { PROJECT_ISSUE_LABELS, PROJECT_MEMBERS, STATES_LIST } from "constants/fetch-keys";
import { IIssueFilterOptions } from "types";
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";

export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
const router = useRouter();
Expand Down Expand Up @@ -60,7 +61,7 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
className="flex items-center gap-x-2 rounded-full border border-brand-base bg-brand-surface-2 px-2 py-1"
>
<span className="capitalize text-brand-secondary">
{replaceUnderscoreIfSnakeCase(key)}:
{key === "target_date" ? "Due Date" : replaceUnderscoreIfSnakeCase(key)}:
</span>
{filters[key as keyof IIssueFilterOptions] === null ||
(filters[key as keyof IIssueFilterOptions]?.length ?? 0) <= 0 ? (
Expand Down Expand Up @@ -299,6 +300,51 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
<XMarkIcon className="h-3 w-3" />
</button>
</div>
) : key === "target_date" ? (
<div className="flex flex-wrap items-center gap-1">
{filters.target_date?.map((date: string) => {
if (filters.target_date.length <= 0) return null;

const splitDate = date.split(";");

return (
<div
key={date}
className="inline-flex items-center gap-x-1 rounded-full border border-brand-base bg-brand-base px-1 py-0.5"
>
<div className="h-1.5 w-1.5 rounded-full" />
<span className="capitalize">
{splitDate[1]} {renderShortDateWithYearFormat(splitDate[0])}
</span>
<span
className="cursor-pointer"
onClick={() =>
setFilters(
{
target_date: filters.target_date?.filter(
(d: any) => d !== date
),
},
!Boolean(viewId)
)
}
>
<XMarkIcon className="h-3 w-3" />
</span>
</div>
);
})}
<button
type="button"
onClick={() =>
setFilters({
target_date: null,
})
}
>
<XMarkIcon className="h-3 w-3" />
</button>
</div>
) : (
(filters[key as keyof IIssueFilterOptions] as any)?.join(", ")
)}
Expand Down Expand Up @@ -332,6 +378,7 @@ export const FilterList: React.FC<any> = ({ filters, setFilters }) => {
assignees: null,
labels: null,
created_by: null,
target_date: null,
})
}
className="flex items-center gap-x-1 rounded-full border border-brand-base bg-brand-surface-2 px-3 py-1.5 text-xs"
Expand Down
4 changes: 4 additions & 0 deletions apps/app/components/core/filters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./due-date-filter-modal";
export * from "./due-date-filter-select";
export * from "./filters-list";
export * from "./issues-view-filter";
Loading