Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions web/components/core/views/all-views.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import useSWR from "swr";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { AppliedFiltersList, CalendarLayout, GanttLayout, KanBanLayout, SpreadsheetLayout } from "components/issues";
import { AppliedFiltersRoot, CalendarLayout, GanttLayout, KanBanLayout, SpreadsheetLayout } from "components/issues";

export const AllViews: React.FC = observer(() => {
const router = useRouter();
Expand Down Expand Up @@ -35,7 +35,7 @@ export const AllViews: React.FC = observer(() => {

return (
<div className="relative w-full h-full flex flex-col overflow-auto">
<AppliedFiltersList />
<AppliedFiltersRoot />
{activeLayout === "kanban" ? (
<KanBanLayout />
) : activeLayout === "calendar" ? (
Expand Down
2 changes: 1 addition & 1 deletion web/components/issues/issue-layouts/calendar/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
<CalendarWeekHeader />
<div className="h-full w-full overflow-y-auto">
{calendarLayout === "month" ? (
<div className="h-full w-full grid grid-cols-1">
<div className="h-full w-full grid grid-cols-1 divide-y-[0.5px] divide-custom-border-200">
{allWeeksOfActiveMonth &&
Object.values(allWeeksOfActiveMonth).map((week: ICalendarWeek, weekIndex) => (
<CalendarWeekDays key={weekIndex} week={week} issues={issues} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { observer } from "mobx-react-lite";

// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// icons
import { ChevronLeft, ChevronRight } from "lucide-react";
// constants
import { MONTHS_LIST } from "constants/calendar";
import { ChevronLeft, ChevronRight } from "lucide-react";

export const CalendarMonthsDropdown: React.FC = observer(() => {
const { calendar: calendarStore, issueFilter: issueFilterStore } = useMobxStore();
Expand Down Expand Up @@ -46,73 +47,63 @@ export const CalendarMonthsDropdown: React.FC = observer(() => {

return (
<Popover className="relative">
{({ close }) => (
<>
<Popover.Button className="outline-none text-xl font-semibold" disabled={calendarLayout === "week"}>
{calendarLayout === "month"
? `${MONTHS_LIST[activeMonthDate.getMonth() + 1].title} ${activeMonthDate.getFullYear()}`
: getWeekLayoutHeader()}
</Popover.Button>
<Transition
as={React.Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel>
<div className="absolute left-0 z-10 mt-1 bg-custom-background-100 border border-custom-border-200 shadow-custom-shadow-rg rounded w-56 p-3 divide-y divide-custom-border-200">
<div className="flex items-center justify-between gap-2 pb-3">
<button
type="button"
className="grid place-items-center"
onClick={() => {
const previousYear = new Date(activeMonthDate.getFullYear() - 1, activeMonthDate.getMonth(), 1);
handleDateChange(previousYear);

close();
}}
>
<ChevronLeft size={14} />
</button>
<span className="text-xs">{activeMonthDate.getFullYear()}</span>
<button
type="button"
className="grid place-items-center"
onClick={() => {
const nextYear = new Date(activeMonthDate.getFullYear() + 1, activeMonthDate.getMonth(), 1);
handleDateChange(nextYear);

close();
}}
>
<ChevronRight size={14} />
</button>
</div>
<div className="grid grid-cols-4 gap-4 items-stretch justify-items-stretch pt-3">
{Object.values(MONTHS_LIST).map((month, index) => (
<button
key={month.shortTitle}
type="button"
className="text-xs hover:bg-custom-background-80 rounded py-0.5"
onClick={() => {
const newDate = new Date(activeMonthDate.getFullYear(), index, 1);
handleDateChange(newDate);

close();
}}
>
{month.shortTitle}
</button>
))}
</div>
</div>
</Popover.Panel>
</Transition>
</>
)}
<Popover.Button className="outline-none text-xl font-semibold" disabled={calendarLayout === "week"}>
{calendarLayout === "month"
? `${MONTHS_LIST[activeMonthDate.getMonth() + 1].title} ${activeMonthDate.getFullYear()}`
: getWeekLayoutHeader()}
</Popover.Button>
<Transition
as={React.Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel>
<div className="absolute left-0 z-10 mt-1 bg-custom-background-100 border border-custom-border-200 shadow-custom-shadow-rg rounded w-56 p-3 divide-y divide-custom-border-200">
<div className="flex items-center justify-between gap-2 pb-3">
<button
type="button"
className="grid place-items-center"
onClick={() => {
const previousYear = new Date(activeMonthDate.getFullYear() - 1, activeMonthDate.getMonth(), 1);
handleDateChange(previousYear);
}}
>
<ChevronLeft size={14} />
</button>
<span className="text-xs">{activeMonthDate.getFullYear()}</span>
<button
type="button"
className="grid place-items-center"
onClick={() => {
const nextYear = new Date(activeMonthDate.getFullYear() + 1, activeMonthDate.getMonth(), 1);
handleDateChange(nextYear);
}}
>
<ChevronRight size={14} />
</button>
</div>
<div className="grid grid-cols-4 gap-4 items-stretch justify-items-stretch pt-3">
{Object.values(MONTHS_LIST).map((month, index) => (
<button
key={month.shortTitle}
type="button"
className="text-xs hover:bg-custom-background-80 rounded py-0.5"
onClick={() => {
const newDate = new Date(activeMonthDate.getFullYear(), index, 1);
handleDateChange(newDate);
}}
>
{month.shortTitle}
</button>
))}
</div>
</div>
</Popover.Panel>
</Transition>
</Popover>
);
});
2 changes: 1 addition & 1 deletion web/components/issues/issue-layouts/calendar/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const CalendarHeader: React.FC = observer(() => {
<div className="flex items-center gap-1.5">
<button
type="button"
className="px-2.5 py-1 text-xs bg-custom-background-80 rounded font-medium"
className="px-2.5 py-1 text-xs bg-custom-background-80 rounded font-medium text-custom-text-200 hover:text-custom-text-100"
onClick={handleToday}
>
Today
Expand Down
6 changes: 3 additions & 3 deletions web/components/issues/issue-layouts/calendar/week-days.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ export const CalendarWeekDays: React.FC<Props> = observer((props) => {

return (
<div
className={`grid divide-x-[0.5px] divide-y-[0.5px] divide-custom-border-200 ${
showWeekends ? "grid-cols-7" : "grid-cols-5"
} ${calendarLayout === "month" ? "" : "h-full"}`}
className={`grid divide-x-[0.5px] divide-custom-border-200 ${showWeekends ? "grid-cols-7" : "grid-cols-5"} ${
calendarLayout === "month" ? "" : "h-full"
}`}
>
{Object.values(week).map((date: ICalendarDate) => {
if (!showWeekends && (date.date.getDay() === 0 || date.date.getDay() === 6)) return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";

// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import {
AppliedDateFilters,
Expand All @@ -17,65 +14,19 @@ import { X } from "lucide-react";
// helpers
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
// types
import { IIssueFilterOptions } from "types";
import { IIssueFilterOptions, IIssueLabels, IStateResponse, IUserLite } from "types";

export const AppliedFiltersList: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
type Props = {
appliedFilters: IIssueFilterOptions;
handleClearAllFilters: () => void;
handleRemoveFilter: (key: keyof IIssueFilterOptions, value: string | null) => void;
labels: IIssueLabels[] | undefined;
members: IUserLite[] | undefined;
states: IStateResponse | undefined;
};

const { issueFilter: issueFilterStore, project: projectStore } = useMobxStore();

const userFilters = issueFilterStore.userFilters;

// filters whose value not null or empty array
const appliedFilters: IIssueFilterOptions = {};
Object.entries(userFilters).forEach(([key, value]) => {
if (!value) return;

if (Array.isArray(value) && value.length === 0) return;

appliedFilters[key as keyof IIssueFilterOptions] = value;
});

const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
if (!workspaceSlug || !projectId) return;

// remove all values of the key if value is null
if (!value) {
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
filters: {
[key]: null,
},
});
return;
}

// remove the passed value from the key
let newValues = issueFilterStore.userFilters?.[key] ?? [];
newValues = newValues.filter((val) => val !== value);

issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
filters: {
[key]: newValues,
},
});
};

const handleClearAllFilters = () => {
if (!workspaceSlug || !projectId) return;

const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null;
});

issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
filters: { ...newFilters },
});
};

// return if no filters are applied
if (Object.keys(appliedFilters).length === 0) return null;
export const AppliedFiltersList: React.FC<Props> = observer((props) => {
const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, members, states } = props;

return (
<div className="flex items-stretch gap-2 flex-wrap bg-custom-background-100 p-4">
Expand All @@ -91,7 +42,7 @@ export const AppliedFiltersList: React.FC = observer(() => {
{(filterKey === "assignees" || filterKey === "created_by" || filterKey === "subscriber") && (
<AppliedMembersFilters
handleRemove={(val) => handleRemoveFilter(filterKey, val)}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
members={members}
values={value}
/>
)}
Expand All @@ -101,7 +52,7 @@ export const AppliedFiltersList: React.FC = observer(() => {
{filterKey === "labels" && (
<AppliedLabelsFilters
handleRemove={(val) => handleRemoveFilter("labels", val)}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []}
labels={labels}
values={value}
/>
)}
Expand All @@ -111,7 +62,7 @@ export const AppliedFiltersList: React.FC = observer(() => {
{filterKey === "state" && (
<AppliedStateFilters
handleRemove={(val) => handleRemoveFilter("state", val)}
states={projectStore.states?.[projectId?.toString() ?? ""]}
states={states}
values={value}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export * from "./filters-list";
export * from "./label";
export * from "./members";
export * from "./priority";
export * from "./root";
export * from "./state";
export * from "./state-group";
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";

// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { AppliedFiltersList } from "components/issues";
// types
import { IIssueFilterOptions } from "types";

export const AppliedFiltersRoot: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;

const { issueFilter: issueFilterStore, project: projectStore } = useMobxStore();

const userFilters = issueFilterStore.userFilters;

// filters whose value not null or empty array
const appliedFilters: IIssueFilterOptions = {};
Object.entries(userFilters).forEach(([key, value]) => {
if (!value) return;

if (Array.isArray(value) && value.length === 0) return;

appliedFilters[key as keyof IIssueFilterOptions] = value;
});

const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
if (!workspaceSlug || !projectId) return;

// remove all values of the key if value is null
if (!value) {
issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
filters: {
[key]: null,
},
});
return;
}

// remove the passed value from the key
let newValues = issueFilterStore.userFilters?.[key] ?? [];
newValues = newValues.filter((val) => val !== value);

issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
filters: {
[key]: newValues,
},
});
};

const handleClearAllFilters = () => {
if (!workspaceSlug || !projectId) return;

const newFilters: IIssueFilterOptions = {};
Object.keys(userFilters).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null;
});

issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
filters: { ...newFilters },
});
};

// return if no filters are applied
if (Object.keys(appliedFilters).length === 0) return null;

return (
<AppliedFiltersList
appliedFilters={appliedFilters}
handleClearAllFilters={handleClearAllFilters}
handleRemoveFilter={handleRemoveFilter}
labels={projectStore.labels?.[projectId?.toString() ?? ""] ?? []}
members={projectStore.members?.[projectId?.toString() ?? ""]?.map((m) => m.member)}
states={projectStore.states?.[projectId?.toString() ?? ""]}
/>
);
});
Loading