feat: react-datepicker added (#210)

* fix: issue description form

* fix: build errors

* feat: react-datepicker added
This commit is contained in:
Aaryan Khandelwal 2023-01-30 23:16:02 +05:30 committed by GitHub
parent fcf23b985b
commit 0ff5f363ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 409 additions and 251 deletions

View File

@ -1,23 +1,26 @@
import React from "react"; import React from "react";
// next
import Link from "next/link"; import Link from "next/link";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// swr
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
// react-beautiful-dnd // react-beautiful-dnd
import { DraggableStateSnapshot } from "react-beautiful-dnd"; import { DraggableStateSnapshot } from "react-beautiful-dnd";
// react-datepicker
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
// headless ui // headless ui
import { Listbox, Transition } from "@headlessui/react"; import { Listbox, Transition } from "@headlessui/react";
// constants // constants
import { TrashIcon } from "@heroicons/react/24/outline"; import { TrashIcon } from "@heroicons/react/24/outline";
import { CalendarDaysIcon } from "@heroicons/react/20/solid";
// services // services
import issuesService from "services/issues.service"; import issuesService from "services/issues.service";
import stateService from "services/state.service"; import stateService from "services/state.service";
import projectService from "services/project.service"; import projectService from "services/project.service";
// components // components
import { CustomSelect, AssigneesList } from "components/ui"; import { AssigneesList, CustomDatePicker } from "components/ui";
// helpers // helpers
import { renderShortNumericDateFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; import { renderShortNumericDateFormat, findHowManyDaysLeft } from "helpers/date-time.helper";
import { addSpaceIfCamelCase } from "helpers/string.helper"; import { addSpaceIfCamelCase } from "helpers/string.helper";
@ -256,7 +259,7 @@ const SingleBoardIssue: React.FC<Props> = ({
)} */} )} */}
{properties.due_date && ( {properties.due_date && (
<div <div
className={`group flex flex-shrink-0 cursor-pointer items-center gap-1 rounded border px-2 py-1 text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${ className={`group relative ${
issue.target_date === null issue.target_date === null
? "" ? ""
: issue.target_date < new Date().toISOString() : issue.target_date < new Date().toISOString()
@ -264,8 +267,37 @@ const SingleBoardIssue: React.FC<Props> = ({
: findHowManyDaysLeft(issue.target_date) <= 3 && "text-orange-400" : findHowManyDaysLeft(issue.target_date) <= 3 && "text-orange-400"
}`} }`}
> >
<CalendarDaysIcon className="h-4 w-4" /> <CustomDatePicker
{issue.target_date ? renderShortNumericDateFormat(issue.target_date) : "N/A"} placeholder="N/A"
value={issue?.target_date}
onChange={(val: Date) => {
partialUpdateIssue({
target_date: val
? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
: null,
});
}}
className={issue?.target_date ? "w-[6.5rem]" : "w-[3rem] text-center"}
/>
{/* <DatePicker
placeholderText="N/A"
value={
issue?.target_date ? `${renderShortNumericDateFormat(issue.target_date)}` : "N/A"
}
selected={issue?.target_date ? new Date(issue.target_date) : null}
onChange={(val: Date) => {
partialUpdateIssue({
target_date: val
? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
: null,
});
}}
dateFormat="dd-MM-yyyy"
className={`cursor-pointer rounded-md border px-2 py-[3px] text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${
issue?.target_date ? "w-[4.5rem]" : "w-[3rem] text-center"
}`}
isClearable
/> */}
</div> </div>
)} )}
{properties.sub_issue_count && ( {properties.sub_issue_count && (

View File

@ -5,6 +5,9 @@ import { useRouter } from "next/router";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
// react-datepicker
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
// services // services
import issuesService from "services/issues.service"; import issuesService from "services/issues.service";
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
@ -12,7 +15,7 @@ import stateService from "services/state.service";
// headless ui // headless ui
import { Listbox, Transition } from "@headlessui/react"; import { Listbox, Transition } from "@headlessui/react";
// ui // ui
import { CustomMenu, CustomSelect, AssigneesList, Avatar } from "components/ui"; import { CustomMenu, CustomSelect, AssigneesList, Avatar, CustomDatePicker } from "components/ui";
// components // components
import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion"; import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion";
// icons // icons
@ -241,7 +244,7 @@ const SingleListIssue: React.FC<Props> = ({
)} */} )} */}
{properties.due_date && ( {properties.due_date && (
<div <div
className={`group relative flex flex-shrink-0 cursor-pointer items-center gap-1 rounded border px-2 py-1 text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${ className={`group relative ${
issue.target_date === null issue.target_date === null
? "" ? ""
: issue.target_date < new Date().toISOString() : issue.target_date < new Date().toISOString()
@ -249,8 +252,37 @@ const SingleListIssue: React.FC<Props> = ({
: findHowManyDaysLeft(issue.target_date) <= 3 && "text-orange-400" : findHowManyDaysLeft(issue.target_date) <= 3 && "text-orange-400"
}`} }`}
> >
<CalendarDaysIcon className="h-4 w-4" /> <CustomDatePicker
{issue.target_date ? renderShortNumericDateFormat(issue.target_date) : "N/A"} placeholder="N/A"
value={issue?.target_date}
onChange={(val: Date) => {
partialUpdateIssue({
target_date: val
? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
: null,
});
}}
className={issue?.target_date ? "w-[6.5rem]" : "w-[3rem] text-center"}
/>
{/* <DatePicker
placeholderText="N/A"
value={
issue?.target_date ? `${renderShortNumericDateFormat(issue.target_date)}` : "N/A"
}
selected={issue?.target_date ? new Date(issue.target_date) : null}
onChange={(val: Date) => {
partialUpdateIssue({
target_date: val
? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
: null,
});
}}
dateFormat="dd-MM-yyyy"
className={`cursor-pointer rounded-md border px-2 py-[3px] text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 ${
issue?.target_date ? "w-[4.5rem]" : "w-[3rem] text-center"
}`}
isClearable
/> */}
<div className="absolute bottom-full right-0 z-10 mb-2 hidden whitespace-nowrap rounded-md bg-white p-2 shadow-md group-hover:block"> <div className="absolute bottom-full right-0 z-10 mb-2 hidden whitespace-nowrap rounded-md bg-white p-2 shadow-md group-hover:block">
<h5 className="mb-1 font-medium text-gray-900">Due date</h5> <h5 className="mb-1 font-medium text-gray-900">Due date</h5>
<div>{renderShortNumericDateFormat(issue.target_date ?? "")}</div> <div>{renderShortNumericDateFormat(issue.target_date ?? "")}</div>

View File

@ -19,7 +19,7 @@ import { CycleSelect as IssueCycleSelect } from "components/cycles/select";
import CreateUpdateStateModal from "components/project/issues/BoardView/state/create-update-state-modal"; import CreateUpdateStateModal from "components/project/issues/BoardView/state/create-update-state-modal";
import CreateUpdateCycleModal from "components/project/cycles/create-update-cycle-modal"; import CreateUpdateCycleModal from "components/project/cycles/create-update-cycle-modal";
// ui // ui
import { Button, CustomMenu, Input, Loader } from "components/ui"; import { Button, CustomDatePicker, CustomMenu, Input, Loader } from "components/ui";
// icons // icons
import { XMarkIcon } from "@heroicons/react/24/outline"; import { XMarkIcon } from "@heroicons/react/24/outline";
// helpers // helpers
@ -289,20 +289,25 @@ export const IssueForm: FC<IssueFormProps> = ({
<IssueLabelSelect value={value} onChange={onChange} projectId={projectId} /> <IssueLabelSelect value={value} onChange={onChange} projectId={projectId} />
)} )}
/> />
<Controller <div>
control={control} <Controller
name="target_date" control={control}
render={({ field: { value, onChange } }) => ( name="target_date"
<input render={({ field: { value, onChange } }) => (
type="date" <CustomDatePicker
value={value ?? ""} value={value}
onChange={(e: any) => { onChange={(val: Date) => {
onChange(e.target.value); onChange(
}} val
className="cursor-pointer rounded-md border px-2 py-[3px] text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" ? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
/> : null
)} );
/> }}
className="max-w-[7rem]"
/>
)}
/>
</div>
<IssueParentSelect <IssueParentSelect
control={control} control={control}
isOpen={parentIssueListModalOpen} isOpen={parentIssueListModalOpen}

View File

@ -4,30 +4,30 @@ import Image from "next/image";
import Module from "public/onboarding/module.png"; import Module from "public/onboarding/module.png";
const BreakIntoModules: React.FC = () => ( const BreakIntoModules: React.FC = () => (
<div className="h-full space-y-4"> <div className="h-full space-y-4">
<div className="relative h-1/2"> <div className="relative h-1/2">
<div <div
className="absolute bottom-0 z-10 h-8 w-full bg-white" className="absolute bottom-0 z-10 h-8 w-full bg-white"
style={{ style={{
background: "linear-gradient(0deg, #fff 84.2%, rgba(255, 255, 255, 0) 34.35%)", background: "linear-gradient(0deg, #fff 84.2%, rgba(255, 255, 255, 0) 34.35%)",
}} }}
/> />
<Image <Image
src={Module} src={Module}
className="h-full" className="h-full"
objectFit="contain" objectFit="contain"
layout="fill" layout="fill"
alt="Plane- Modules" alt="Plane- Modules"
/> />
</div>
<div className="mx-auto h-1/2 space-y-4 lg:w-1/2">
<h2 className="text-2xl font-medium">Break into Modules</h2>
<p className="text-sm text-gray-400">
Modules break your big think into Projects or Features, to help you organize better.
</p>
<p className="text-sm text-gray-400">4/5</p>
</div>
</div> </div>
); <div className="mx-auto h-1/2 space-y-4 lg:w-1/2">
<h2 className="text-2xl font-medium">Break into Modules</h2>
<p className="text-sm text-gray-400">
Modules break your big thing into Projects or Features, to help you organize better.
</p>
<p className="text-sm text-gray-400">4/5</p>
</div>
</div>
);
export default BreakIntoModules; export default BreakIntoModules;

View File

@ -185,7 +185,7 @@ export const CreateProjectModal: React.FC<Props> = (props) => {
</p> </p>
</div> </div>
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center gap-3"> <div className="flex gap-3">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<label htmlFor="icon" className="mb-2 text-gray-500"> <label htmlFor="icon" className="mb-2 text-gray-500">
Icon Icon

View File

@ -1,18 +1,19 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
// next
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// swr
import { mutate } from "swr"; import { mutate } from "swr";
// react hook form // react hook form
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// headless // headless ui
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
// services // services
import cycleService from "services/cycles.service"; import cycleService from "services/cycles.service";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// ui // ui
import { Button, Input, TextArea, CustomSelect } from "components/ui"; import { Button, Input, TextArea, CustomSelect, CustomDatePicker } from "components/ui";
// common // common
import { renderDateFormat } from "helpers/date-time.helper"; import { renderDateFormat } from "helpers/date-time.helper";
// types // types
@ -31,8 +32,8 @@ const defaultValues: Partial<ICycle> = {
name: "", name: "",
description: "", description: "",
status: "draft", status: "draft",
start_date: new Date().toString(), start_date: null,
end_date: new Date().toString(), end_date: null,
}; };
const CreateUpdateCycleModal: React.FC<Props> = ({ isOpen, setIsOpen, data, projectId }) => { const CreateUpdateCycleModal: React.FC<Props> = ({ isOpen, setIsOpen, data, projectId }) => {
@ -202,32 +203,62 @@ const CreateUpdateCycleModal: React.FC<Props> = ({ isOpen, setIsOpen, data, proj
</div> </div>
<div className="flex gap-x-2"> <div className="flex gap-x-2">
<div className="w-full"> <div className="w-full">
<Input <h6 className="text-gray-500">Start Date</h6>
id="start_date" <div className="w-full">
label="Start Date" <Controller
name="start_date" control={control}
type="date" name="start_date"
placeholder="Enter start date" rules={{ required: "Start date is required" }}
error={errors.start_date} render={({ field: { value, onChange } }) => (
register={register} <CustomDatePicker
validations={{ renderAs="input"
required: "Start date is required", value={value}
}} onChange={(val: Date) => {
/> onChange(
val
? `${val.getFullYear()}-${
val.getMonth() + 1
}-${val.getDate()}`
: null
);
}}
error={errors.start_date ? true : false}
/>
)}
/>
{errors.start_date && (
<h6 className="text-sm text-red-500">{errors.start_date.message}</h6>
)}
</div>
</div> </div>
<div className="w-full"> <div className="w-full">
<Input <h6 className="text-gray-500">End Date</h6>
id="end_date" <div className="w-full">
label="End Date" <Controller
name="end_date" control={control}
type="date" name="end_date"
placeholder="Enter end date" rules={{ required: "End date is required" }}
error={errors.end_date} render={({ field: { value, onChange } }) => (
register={register} <CustomDatePicker
validations={{ renderAs="input"
required: "End date is required", value={value}
}} onChange={(val: Date) => {
/> onChange(
val
? `${val.getFullYear()}-${
val.getMonth() + 1
}-${val.getDate()}`
: null
);
}}
error={errors.end_date ? true : false}
/>
)}
/>
{errors.end_date && (
<h6 className="text-sm text-red-500">{errors.end_date.message}</h6>
)}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -13,7 +13,7 @@ import cyclesService from "services/cycles.service";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// ui // ui
import { Loader } from "components/ui"; import { Loader, CustomDatePicker } from "components/ui";
//progress-bar //progress-bar
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";
@ -23,7 +23,7 @@ import { groupBy } from "helpers/array.helper";
// types // types
import { CycleIssueResponse, ICycle } from "types"; import { CycleIssueResponse, ICycle } from "types";
// fetch-keys // fetch-keys
import { CYCLE_DETAIL } from "constants/fetch-keys"; import { CYCLE_LIST } from "constants/fetch-keys";
type Props = { type Props = {
cycle: ICycle | undefined; cycle: ICycle | undefined;
@ -38,9 +38,7 @@ const defaultValues: Partial<ICycle> = {
const CycleDetailSidebar: React.FC<Props> = ({ cycle, isOpen, cycleIssues }) => { const CycleDetailSidebar: React.FC<Props> = ({ cycle, isOpen, cycleIssues }) => {
const router = useRouter(); const router = useRouter();
const { const { workspaceSlug, projectId, cycleId } = router.query;
query: { workspaceSlug, projectId },
} = router;
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
@ -60,11 +58,21 @@ const CycleDetailSidebar: React.FC<Props> = ({ cycle, isOpen, cycleIssues }) =>
const submitChanges = (data: Partial<ICycle>) => { const submitChanges = (data: Partial<ICycle>) => {
if (!workspaceSlug || !projectId || !module) return; if (!workspaceSlug || !projectId || !module) return;
mutate<ICycle[]>(
projectId && CYCLE_LIST(projectId as string),
(prevData) =>
(prevData ?? []).map((tempCycle) => {
if (tempCycle.id === cycleId) return { ...tempCycle, ...data };
return tempCycle;
}),
false
);
cyclesService cyclesService
.patchCycle(workspaceSlug as string, projectId as string, cycle?.id ?? "", data) .patchCycle(workspaceSlug as string, projectId as string, cycle?.id ?? "", data)
.then((res) => { .then((res) => {
console.log(res); console.log(res);
mutate(CYCLE_DETAIL); mutate(CYCLE_LIST(projectId as string));
}) })
.catch((e) => { .catch((e) => {
console.log(e); console.log(e);
@ -160,16 +168,17 @@ const CycleDetailSidebar: React.FC<Props> = ({ cycle, isOpen, cycleIssues }) =>
<Controller <Controller
control={control} control={control}
name="start_date" name="start_date"
render={({ field: { value, onChange } }) => ( render={({ field: { value } }) => (
<input <CustomDatePicker
type="date" value={value}
id="cycleStartDate" onChange={(val: Date) => {
value={value ?? ""} submitChanges({
onChange={(e: any) => { start_date: val
submitChanges({ start_date: e.target.value }); ? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
onChange(e.target.value); : null,
});
}} }}
className="w-full cursor-pointer rounded-md border bg-transparent px-2 py-1 text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" isClearable={false}
/> />
)} )}
/> />
@ -184,16 +193,17 @@ const CycleDetailSidebar: React.FC<Props> = ({ cycle, isOpen, cycleIssues }) =>
<Controller <Controller
control={control} control={control}
name="end_date" name="end_date"
render={({ field: { value, onChange } }) => ( render={({ field: { value } }) => (
<input <CustomDatePicker
type="date" value={value}
id="moduleEndDate" onChange={(val: Date) => {
value={value ?? ""} submitChanges({
onChange={(e: any) => { end_date: val
submitChanges({ end_date: e.target.value }); ? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
onChange(e.target.value); : null,
});
}} }}
className="w-full cursor-pointer rounded-md border bg-transparent px-2 py-1 text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" isClearable={false}
/> />
)} )}
/> />

View File

@ -68,7 +68,8 @@ const BoardView: React.FC<Props> = ({ issues, handleDeleteIssue, userAuth }) =>
const handleOnDragEnd = useCallback( const handleOnDragEnd = useCallback(
(result: DropResult) => { (result: DropResult) => {
if (!result.destination) return; if (!result.destination || !workspaceSlug || !projectId) return;
const { source, destination, type } = result; const { source, destination, type } = result;
if (destination.droppableId === "trashBox") { if (destination.droppableId === "trashBox") {
@ -94,7 +95,7 @@ const BoardView: React.FC<Props> = ({ issues, handleDeleteIssue, userAuth }) =>
newStates[destination.index].sequence = sequenceNumber; newStates[destination.index].sequence = sequenceNumber;
mutateState(newStates, false); mutateState(newStates, false);
if (!workspaceSlug) return;
stateServices stateServices
.patchState( .patchState(
workspaceSlug as string, workspaceSlug as string,
@ -140,18 +141,6 @@ const BoardView: React.FC<Props> = ({ issues, handleDeleteIssue, userAuth }) =>
draggedItem.state = destinationStateId; draggedItem.state = destinationStateId;
draggedItem.state_detail = destinationState; draggedItem.state_detail = destinationState;
// patch request
issuesServices.patchIssue(
workspaceSlug as string,
projectId as string,
draggedItem.id,
{
state: destinationStateId,
}
);
// mutate the issues
if (!workspaceSlug || !projectId) return;
mutate<IssueResponse>( mutate<IssueResponse>(
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string), PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
(prevData) => { (prevData) => {
@ -175,6 +164,15 @@ const BoardView: React.FC<Props> = ({ issues, handleDeleteIssue, userAuth }) =>
}, },
false false
); );
// patch request
issuesServices
.patchIssue(workspaceSlug as string, projectId as string, draggedItem.id, {
state: destinationStateId,
})
.then((res) => {
mutate(PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string));
});
} }
} }
} }

View File

@ -76,11 +76,6 @@ const activityDetails: {
}, },
}; };
const defaultValues: Partial<IIssueComment> = {
comment_html: "",
comment_json: "",
};
const IssueActivitySection: React.FC<{ const IssueActivitySection: React.FC<{
issueActivities: IIssueActivity[]; issueActivities: IIssueActivity[];
mutate: KeyedMutator<IIssueActivity[]>; mutate: KeyedMutator<IIssueActivity[]>;
@ -99,7 +94,7 @@ const IssueActivitySection: React.FC<{
comment.id, comment.id,
comment comment
) )
.then((response) => { .then((res) => {
mutate(); mutate();
}); });
}; };
@ -180,6 +175,10 @@ const IssueActivitySection: React.FC<{
? activity.new_value !== "" ? activity.new_value !== ""
? "marked this issue being blocked by" ? "marked this issue being blocked by"
: "removed blocker" : "removed blocker"
: activity.field === "target_date"
? activity.new_value && activity.new_value !== ""
? "set the due date to"
: "removed the due date"
: activityDetails[activity.field as keyof typeof activityDetails] : activityDetails[activity.field as keyof typeof activityDetails]
?.message}{" "} ?.message}{" "}
</span> </span>
@ -203,7 +202,9 @@ const IssueActivitySection: React.FC<{
) : activity.field === "assignee" ? ( ) : activity.field === "assignee" ? (
activity.old_value activity.old_value
) : activity.field === "target_date" ? ( ) : activity.field === "target_date" ? (
renderShortNumericDateFormat(activity.new_value as string) activity.new_value ? (
renderShortNumericDateFormat(activity.new_value as string)
) : null
) : activity.field === "description" ? ( ) : activity.field === "description" ? (
"" ""
) : ( ) : (

View File

@ -4,20 +4,12 @@ import { useRouter } from "next/router";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
// react-hook-form
import { useForm, Controller, UseFormWatch, Control } from "react-hook-form"; import { useForm, Controller, UseFormWatch, Control } from "react-hook-form";
// react-color
import { TwitterPicker } from "react-color"; import { TwitterPicker } from "react-color";
// services // headless ui
import { Popover, Listbox, Transition } from "@headlessui/react"; import { Popover, Listbox, Transition } from "@headlessui/react";
import {
TagIcon,
ChevronDownIcon,
LinkIcon,
CalendarDaysIcon,
TrashIcon,
PlusIcon,
XMarkIcon,
} from "@heroicons/react/24/outline";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// services // services
@ -31,11 +23,19 @@ import SelectCycle from "components/project/issues/issue-detail/issue-detail-sid
import SelectAssignee from "components/project/issues/issue-detail/issue-detail-sidebar/select-assignee"; import SelectAssignee from "components/project/issues/issue-detail/issue-detail-sidebar/select-assignee";
import SelectBlocker from "components/project/issues/issue-detail/issue-detail-sidebar/select-blocker"; import SelectBlocker from "components/project/issues/issue-detail/issue-detail-sidebar/select-blocker";
import SelectBlocked from "components/project/issues/issue-detail/issue-detail-sidebar/select-blocked"; import SelectBlocked from "components/project/issues/issue-detail/issue-detail-sidebar/select-blocked";
// headless ui
// ui // ui
import { Input, Button, Spinner } from "components/ui"; import { Input, Button, Spinner, CustomDatePicker } from "components/ui";
import DatePicker from "react-datepicker"; import DatePicker from "react-datepicker";
// icons // icons
import {
TagIcon,
ChevronDownIcon,
LinkIcon,
CalendarDaysIcon,
TrashIcon,
PlusIcon,
XMarkIcon,
} from "@heroicons/react/24/outline";
// helpers // helpers
import { copyTextToClipboard } from "helpers/string.helper"; import { copyTextToClipboard } from "helpers/string.helper";
// types // types
@ -240,16 +240,16 @@ const IssueDetailSidebar: React.FC<Props> = ({
<Controller <Controller
control={control} control={control}
name="target_date" name="target_date"
render={({ field: { value, onChange } }) => ( render={({ field: { value } }) => (
<input <CustomDatePicker
type="date" value={value}
id="issueDate" onChange={(val: Date) => {
value={value ?? ""} submitChanges({
onChange={(e: any) => { target_date: val
submitChanges({ target_date: e.target.value }); ? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
onChange(e.target.value); : null,
});
}} }}
className="w-full cursor-pointer rounded-md border px-2 py-1 text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
/> />
)} )}
/> />

View File

@ -5,7 +5,7 @@ import { useRouter } from "next/router";
import { mutate } from "swr"; import { mutate } from "swr";
// react-hook-form // react-hook-form
import { useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// headless ui // headless ui
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
// components // components
@ -13,7 +13,7 @@ import SelectLead from "components/project/modules/create-update-module-modal/se
import SelectMembers from "components/project/modules/create-update-module-modal/select-members"; import SelectMembers from "components/project/modules/create-update-module-modal/select-members";
import SelectStatus from "components/project/modules/create-update-module-modal/select-status"; import SelectStatus from "components/project/modules/create-update-module-modal/select-status";
// ui // ui
import { Button, Input, TextArea } from "components/ui"; import { Button, CustomDatePicker, Input, TextArea } from "components/ui";
// services // services
import modulesService from "services/modules.service"; import modulesService from "services/modules.service";
// hooks // hooks
@ -193,32 +193,62 @@ const CreateUpdateModuleModal: React.FC<Props> = ({ isOpen, setIsOpen, data, pro
</div> </div>
<div className="flex gap-x-2"> <div className="flex gap-x-2">
<div className="w-full"> <div className="w-full">
<Input <h6 className="text-gray-500">Start Date</h6>
id="start_date" <div className="w-full">
label="Start Date" <Controller
name="start_date" control={control}
type="date" name="start_date"
placeholder="Enter start date" rules={{ required: "Start date is required" }}
error={errors.start_date} render={({ field: { value, onChange } }) => (
register={register} <CustomDatePicker
validations={{ renderAs="input"
required: "Start date is required", value={value}
}} onChange={(val: Date) => {
/> onChange(
val
? `${val.getFullYear()}-${
val.getMonth() + 1
}-${val.getDate()}`
: null
);
}}
error={errors.start_date ? true : false}
/>
)}
/>
{errors.start_date && (
<h6 className="text-sm text-red-500">{errors.start_date.message}</h6>
)}
</div>
</div> </div>
<div className="w-full"> <div className="w-full">
<Input <h6 className="text-gray-500">Target Date</h6>
id="target_date" <div className="w-full">
label="Target Date" <Controller
name="target_date" control={control}
type="date" name="target_date"
placeholder="Enter target date" rules={{ required: "Target date is required" }}
error={errors.target_date} render={({ field: { value, onChange } }) => (
register={register} <CustomDatePicker
validations={{ renderAs="input"
required: "Target date is required", value={value}
}} onChange={(val: Date) => {
/> onChange(
val
? `${val.getFullYear()}-${
val.getMonth() + 1
}-${val.getDate()}`
: null
);
}}
error={errors.target_date ? true : false}
/>
)}
/>
{errors.target_date && (
<h6 className="text-sm text-red-500">{errors.target_date.message}</h6>
)}
</div>
</div> </div>
</div> </div>
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">

View File

@ -26,7 +26,7 @@ import ModuleLinkModal from "components/project/modules/module-link-modal";
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";
// ui // ui
import { Loader } from "components/ui"; import { CustomDatePicker, Loader } from "components/ui";
// icons // icons
// helpers // helpers
import { timeAgo } from "helpers/date-time.helper"; import { timeAgo } from "helpers/date-time.helper";
@ -39,8 +39,8 @@ import { MODULE_LIST } from "constants/fetch-keys";
const defaultValues: Partial<IModule> = { const defaultValues: Partial<IModule> = {
members_list: [], members_list: [],
start_date: new Date().toString(), start_date: null,
target_date: new Date().toString(), target_date: null,
status: null, status: null,
}; };
@ -88,16 +88,21 @@ const ModuleDetailSidebar: React.FC<Props> = ({
const submitChanges = (data: Partial<IModule>) => { const submitChanges = (data: Partial<IModule>) => {
if (!workspaceSlug || !projectId || !module) return; if (!workspaceSlug || !projectId || !module) return;
mutate<IModule[]>(
projectId && MODULE_LIST(projectId as string),
(prevData) =>
(prevData ?? []).map((module) => {
if (module.id === moduleId) return { ...module, ...data };
return module;
}),
false
);
modulesService modulesService
.patchModule(workspaceSlug as string, projectId as string, module.id, data) .patchModule(workspaceSlug as string, projectId as string, module.id, data)
.then((res) => { .then((res) => {
console.log(res); console.log(res);
mutate<IModule[]>(projectId && MODULE_LIST(projectId as string), (prevData) => mutate(MODULE_LIST(projectId as string));
(prevData ?? []).map((module) => {
if (module.id === moduleId) return { ...module, ...data };
return module;
})
);
}) })
.catch((e) => { .catch((e) => {
console.log(e); console.log(e);
@ -186,16 +191,16 @@ const ModuleDetailSidebar: React.FC<Props> = ({
<Controller <Controller
control={control} control={control}
name="start_date" name="start_date"
render={({ field: { value, onChange } }) => ( render={({ field: { value } }) => (
<input <CustomDatePicker
type="date" value={value}
id="moduleStartDate" onChange={(val: Date) => {
value={value ?? ""} submitChanges({
onChange={(e: any) => { start_date: val
submitChanges({ start_date: e.target.value }); ? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
onChange(e.target.value); : null,
});
}} }}
className="w-full cursor-pointer rounded-md border bg-transparent px-2 py-1 text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
/> />
)} )}
/> />
@ -210,16 +215,16 @@ const ModuleDetailSidebar: React.FC<Props> = ({
<Controller <Controller
control={control} control={control}
name="target_date" name="target_date"
render={({ field: { value, onChange } }) => ( render={({ field: { value } }) => (
<input <CustomDatePicker
type="date" value={value}
id="moduleTargetDate" onChange={(val: Date) => {
value={value ?? ""} submitChanges({
onChange={(e: any) => { target_date: val
submitChanges({ target_date: e.target.value }); ? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
onChange(e.target.value); : null,
});
}} }}
className="w-full cursor-pointer rounded-md border bg-transparent px-2 py-1 text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
/> />
)} )}
/> />

View File

@ -12,9 +12,7 @@ import { UserGroupIcon } from "@heroicons/react/24/outline";
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
// headless ui // headless ui
// ui // ui
import { Spinner } from "components/ui"; import { AssigneesList, Spinner } from "components/ui";
// icons
import User from "public/user.png";
// types // types
import { IModule } from "types"; import { IModule } from "types";
// constants // constants
@ -64,52 +62,7 @@ const SelectMembers: React.FC<Props> = ({ control, submitChanges }) => {
> >
<div className="flex cursor-pointer items-center gap-1 text-xs"> <div className="flex cursor-pointer items-center gap-1 text-xs">
{value && Array.isArray(value) ? ( {value && Array.isArray(value) ? (
<> <AssigneesList userIds={value} length={10} />
{value.length > 0 ? (
value.map((member, index: number) => {
const person = people?.find((p) => p.member.id === member)?.member;
return (
<div
key={index}
className={`relative z-[1] h-5 w-5 rounded-full ${
index !== 0 ? "-ml-2.5" : ""
}`}
>
{person && person.avatar && person.avatar !== "" ? (
<div className="h-5 w-5 rounded-full border-2 border-white bg-white">
<Image
src={person.avatar}
height="100%"
width="100%"
className="rounded-full"
alt={person.first_name}
/>
</div>
) : (
<div
className={`grid h-5 w-5 place-items-center rounded-full border-2 border-white bg-gray-700 capitalize text-white`}
>
{person?.first_name && person.first_name !== ""
? person.first_name.charAt(0)
: person?.email.charAt(0)}
</div>
)}
</div>
);
})
) : (
<div className="h-5 w-5 rounded-full border-2 border-white bg-white">
<Image
src={User}
height="100%"
width="100%"
className="rounded-full"
alt="No user"
/>
</div>
)}
</>
) : null} ) : null}
</div> </div>
</span> </span>

View File

@ -0,0 +1,40 @@
// react-datepicker
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
type Props = {
renderAs?: "input" | "button";
value: Date | string | null | undefined;
onChange: (arg: Date) => void;
placeholder?: string;
displayShortForm?: boolean;
error?: boolean;
className?: string;
isClearable?: boolean;
};
export const CustomDatePicker: React.FC<Props> = ({
renderAs = "button",
value,
onChange,
placeholder = "Select date",
displayShortForm = false,
error = false,
className = "",
isClearable = true,
}) => (
<DatePicker
placeholderText={placeholder}
selected={value ? new Date(value) : null}
onChange={onChange}
dateFormat="dd-MM-yyyy"
className={`${className} ${
renderAs === "input"
? "block bg-transparent text-sm focus:outline-none rounded-md border border-gray-300 px-3 py-2 w-full cursor-pointer"
: renderAs === "button"
? "w-full cursor-pointer rounded-md border px-2 py-1 text-xs shadow-sm duration-300 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
: ""
} ${error ? "border-red-500 bg-red-200" : ""} bg-transparent caret-transparent`}
isClearable={isClearable}
/>
);

View File

@ -13,3 +13,4 @@ export * from "./spinner";
export * from "./text-area"; export * from "./text-area";
export * from "./tooltip"; export * from "./tooltip";
export * from "./avatar"; export * from "./avatar";
export * from "./datepicker";

View File

@ -50,7 +50,7 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
const [selectedIssues, setSelectedIssues] = useState<SelectIssue>(); const [selectedIssues, setSelectedIssues] = useState<SelectIssue>();
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false); const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
const [deleteIssue, setDeleteIssue] = useState<string | undefined>(undefined); const [deleteIssue, setDeleteIssue] = useState<string | undefined>(undefined);
const [cycleSidebar, setCycleSidebar] = useState(false); const [cycleSidebar, setCycleSidebar] = useState(true);
const [preloadedData, setPreloadedData] = useState< const [preloadedData, setPreloadedData] = useState<
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | null (Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | null

View File

@ -71,3 +71,23 @@
border: none; border: none;
outline: none; outline: none;
} }
/* react datepicker styling */
.react-datepicker-wrapper input::placeholder {
color: #000;
opacity: 1;
}
.react-datepicker-wrapper input:-ms-input-placeholder {
color: #000;
}
.react-datepicker-wrapper .react-datepicker__close-icon::after {
background: transparent;
color: #000;
}
.react-datepicker-popper {
z-index: 30 !important;
}
/* end react datepicker styling */