feat : cycle sidebar revamp (#271)

* feat: range date picker added

* feat: cycle status ui improved
This commit is contained in:
Anmol Singh Bhatia 2023-02-13 10:33:02 +05:30 committed by GitHub
parent d0afa486c7
commit ebf294af55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 99 additions and 143 deletions

View File

@ -1,4 +1,4 @@
import { useEffect } from "react"; import React, { useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Image from "next/image"; import Image from "next/image";
@ -10,11 +10,18 @@ import { Controller, useForm } from "react-hook-form";
// react-circular-progressbar // react-circular-progressbar
import { CircularProgressbar } from "react-circular-progressbar"; import { CircularProgressbar } from "react-circular-progressbar";
import "react-circular-progressbar/dist/styles.css"; import "react-circular-progressbar/dist/styles.css";
import { Popover, Transition } from "@headlessui/react";
import DatePicker from "react-datepicker";
// icons // icons
import { CalendarDaysIcon, ChartPieIcon, LinkIcon, UserIcon } from "@heroicons/react/24/outline"; import {
import { CycleSidebarStatusSelect } from "components/project/cycles"; CalendarDaysIcon,
ChartPieIcon,
LinkIcon,
Squares2X2Icon,
UserIcon,
} from "@heroicons/react/24/outline";
// ui // ui
import { Loader, CustomDatePicker } from "components/ui"; import { CustomSelect, Loader } from "components/ui";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// services // services
@ -24,12 +31,13 @@ import { SidebarProgressStats } from "components/core";
// helpers // helpers
import { copyTextToClipboard } from "helpers/string.helper"; import { copyTextToClipboard } from "helpers/string.helper";
import { groupBy } from "helpers/array.helper"; import { groupBy } from "helpers/array.helper";
import { renderShortNumericDateFormat } from "helpers/date-time.helper"; import { renderDateFormat, renderShortNumericDateFormat } from "helpers/date-time.helper";
// types // types
import { CycleIssueResponse, ICycle, IIssue } from "types"; import { CycleIssueResponse, ICycle, IIssue } from "types";
// fetch-keys // fetch-keys
import { CYCLE_DETAILS } from "constants/fetch-keys"; import { CYCLE_DETAILS } from "constants/fetch-keys";
import ProgressChart from "components/core/sidebar/progress-chart"; import ProgressChart from "components/core/sidebar/progress-chart";
import { CYCLE_STATUS } from "constants/cycle";
type Props = { type Props = {
issues: IIssue[]; issues: IIssue[];
@ -42,6 +50,9 @@ const CycleDetailSidebar: React.FC<Props> = ({ issues, cycle, isOpen, cycleIssue
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId } = router.query; const { workspaceSlug, projectId, cycleId } = router.query;
const [startDateRange, setStartDateRange] = useState<Date | null>(new Date());
const [endDateRange, setEndDateRange] = useState<Date | null>(null);
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const defaultValues: Partial<ICycle> = { const defaultValues: Partial<ICycle> = {
@ -98,11 +109,44 @@ const CycleDetailSidebar: React.FC<Props> = ({ issues, cycle, isOpen, cycleIssue
> >
{cycle ? ( {cycle ? (
<> <>
<div className="flex gap-2 text-sm my-2"> <div className="flex gap-1 text-sm my-2">
<div className="px-2 py-1 rounded bg-gray-200"> <div className="flex items-center ">
<span className="capitalize">{cycle.status}</span> <Controller
control={control}
name="status"
render={({ field: { value } }) => (
<CustomSelect
label={
<span
className={`flex items-center gap-1 text-left capitalize p-1 text-xs h-full w-full text-gray-900`}
>
<Squares2X2Icon className="h-4 w-4 flex-shrink-0" />
{watch("status")}
</span>
}
value={value}
onChange={(value: any) => {
submitChanges({ status: value });
}}
>
{CYCLE_STATUS.map((option) => (
<CustomSelect.Option key={option.value} value={option.value}>
<span className="text-xs">{option.label}</span>
</CustomSelect.Option>
))}
</CustomSelect>
)}
/>
</div> </div>
<div className="px-2 py-1 rounded bg-gray-200"> <Popover className="flex justify-center items-center relative rounded-lg">
{({ open }) => (
<>
<Popover.Button
className={`group flex items-center gap-2 rounded-md border bg-transparent h-full w-full p-2 px-4 text-xs font-medium text-gray-900 hover:bg-gray-100 hover:text-gray-900 focus:outline-none ${
open ? "bg-gray-100" : ""
}`}
>
<CalendarDaysIcon className="h-4 w-4 flex-shrink-0" />
<span> <span>
{renderShortNumericDateFormat(`${cycle.start_date}`) {renderShortNumericDateFormat(`${cycle.start_date}`)
? renderShortNumericDateFormat(`${cycle.start_date}`) ? renderShortNumericDateFormat(`${cycle.start_date}`)
@ -112,7 +156,43 @@ const CycleDetailSidebar: React.FC<Props> = ({ issues, cycle, isOpen, cycleIssue
? renderShortNumericDateFormat(`${cycle.end_date}`) ? renderShortNumericDateFormat(`${cycle.end_date}`)
: "N/A"} : "N/A"}
</span> </span>
</div> </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 className="absolute top-10 left-0 z-20 transform overflow-hidden">
<DatePicker
selected={startDateRange}
onChange={(dates) => {
const [start, end] = dates;
submitChanges({
start_date: renderDateFormat(start),
end_date: renderDateFormat(end),
});
if (setStartDateRange) {
setStartDateRange(start);
}
if (setEndDateRange) {
setEndDateRange(end);
}
}}
startDate={startDateRange}
endDate={endDateRange}
selectsRange
inline
/>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
</div> </div>
<div className="flex items-center justify-between pb-3"> <div className="flex items-center justify-between pb-3">
<h4 className="text-sm font-medium">{cycle.name}</h4> <h4 className="text-sm font-medium">{cycle.name}</h4>
@ -192,59 +272,6 @@ const CycleDetailSidebar: React.FC<Props> = ({ issues, cycle, isOpen, cycleIssue
</div> </div>
</div> </div>
</div> </div>
<div className="py-1">
<div className="flex flex-wrap items-center py-2">
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
<CalendarDaysIcon className="h-4 w-4 flex-shrink-0" />
<p>Start date</p>
</div>
<div className="sm:basis-1/2">
<Controller
control={control}
name="start_date"
render={({ field: { value } }) => (
<CustomDatePicker
value={value}
onChange={(val) =>
submitChanges({
start_date: val,
})
}
isClearable={false}
/>
)}
/>
</div>
</div>
<div className="flex flex-wrap items-center py-2">
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
<CalendarDaysIcon className="h-4 w-4 flex-shrink-0" />
<p>End date</p>
</div>
<div className="sm:basis-1/2">
<Controller
control={control}
name="end_date"
render={({ field: { value } }) => (
<CustomDatePicker
value={value}
onChange={(val) =>
submitChanges({
end_date: val,
})
}
isClearable={false}
/>
)}
/>
</div>
</div>
<CycleSidebarStatusSelect
control={control}
submitChanges={submitChanges}
watch={watch}
/>
</div>
<div className="py-1" /> <div className="py-1" />
</div> </div>
<div className="flex flex-col items-center justify-center w-full gap-2 "> <div className="flex flex-col items-center justify-center w-full gap-2 ">

View File

@ -1,3 +1,2 @@
export * from "./sidebar-select";
export * from "./stats-view"; export * from "./stats-view";
export * from "./cycle-detail-sidebar"; export * from "./cycle-detail-sidebar";

View File

@ -1 +0,0 @@
export * from "./select-status";

View File

@ -1,69 +0,0 @@
// react
import React from "react";
// react-hook-form
import { Control, Controller, UseFormWatch } from "react-hook-form";
// icons
import { Squares2X2Icon } from "@heroicons/react/24/outline";
// ui
import { CustomSelect } from "components/ui";
// types
import { ICycle } from "types";
// common
// constants
import { CYCLE_STATUS } from "constants/cycle";
type Props = {
control: Control<Partial<ICycle>, any>;
submitChanges: (formData: Partial<ICycle>) => void;
watch: UseFormWatch<Partial<ICycle>>;
};
export const CycleSidebarStatusSelect: React.FC<Props> = ({ control, submitChanges, watch }) => (
<div className="flex flex-wrap items-center py-2">
<div className="flex items-center gap-x-2 text-sm sm:basis-1/2">
<Squares2X2Icon className="h-4 w-4 flex-shrink-0" />
<p>Status</p>
</div>
<div className="sm:basis-1/2">
<Controller
control={control}
name="status"
render={({ field: { value } }) => (
<CustomSelect
label={
<span
className={`flex items-center gap-2 text-left capitalize ${
value ? "" : "text-gray-900"
}`}
>
<span
className="h-2 w-2 flex-shrink-0 rounded-full"
style={{
backgroundColor: CYCLE_STATUS?.find((option) => option.value === value)?.color,
}}
/>
{watch("status")}
</span>
}
value={value}
onChange={(value: any) => {
submitChanges({ status: value });
}}
>
{CYCLE_STATUS.map((option) => (
<CustomSelect.Option key={option.value} value={option.value}>
<>
<span
className="h-2 w-2 flex-shrink-0 rounded-full"
style={{ backgroundColor: option.color }}
/>
{option.label}
</>
</CustomSelect.Option>
))}
</CustomSelect>
)}
/>
</div>
</div>
);