mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
feat: react-datepicker added (#210)
* fix: issue description form * fix: build errors * feat: react-datepicker added
This commit is contained in:
parent
fcf23b985b
commit
0ff5f363ee
@ -1,23 +1,26 @@
|
||||
import React from "react";
|
||||
// next
|
||||
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
// swr
|
||||
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
// 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
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
// constants
|
||||
import { TrashIcon } from "@heroicons/react/24/outline";
|
||||
import { CalendarDaysIcon } from "@heroicons/react/20/solid";
|
||||
// services
|
||||
import issuesService from "services/issues.service";
|
||||
import stateService from "services/state.service";
|
||||
import projectService from "services/project.service";
|
||||
// components
|
||||
import { CustomSelect, AssigneesList } from "components/ui";
|
||||
import { AssigneesList, CustomDatePicker } from "components/ui";
|
||||
// helpers
|
||||
import { renderShortNumericDateFormat, findHowManyDaysLeft } from "helpers/date-time.helper";
|
||||
import { addSpaceIfCamelCase } from "helpers/string.helper";
|
||||
@ -256,7 +259,7 @@ const SingleBoardIssue: React.FC<Props> = ({
|
||||
)} */}
|
||||
{properties.due_date && (
|
||||
<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 < new Date().toISOString()
|
||||
@ -264,8 +267,37 @@ const SingleBoardIssue: React.FC<Props> = ({
|
||||
: findHowManyDaysLeft(issue.target_date) <= 3 && "text-orange-400"
|
||||
}`}
|
||||
>
|
||||
<CalendarDaysIcon className="h-4 w-4" />
|
||||
{issue.target_date ? renderShortNumericDateFormat(issue.target_date) : "N/A"}
|
||||
<CustomDatePicker
|
||||
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>
|
||||
)}
|
||||
{properties.sub_issue_count && (
|
||||
|
@ -5,6 +5,9 @@ import { useRouter } from "next/router";
|
||||
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
// react-datepicker
|
||||
import DatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
// services
|
||||
import issuesService from "services/issues.service";
|
||||
import workspaceService from "services/workspace.service";
|
||||
@ -12,7 +15,7 @@ import stateService from "services/state.service";
|
||||
// headless ui
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
// ui
|
||||
import { CustomMenu, CustomSelect, AssigneesList, Avatar } from "components/ui";
|
||||
import { CustomMenu, CustomSelect, AssigneesList, Avatar, CustomDatePicker } from "components/ui";
|
||||
// components
|
||||
import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion";
|
||||
// icons
|
||||
@ -241,7 +244,7 @@ const SingleListIssue: React.FC<Props> = ({
|
||||
)} */}
|
||||
{properties.due_date && (
|
||||
<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 < new Date().toISOString()
|
||||
@ -249,8 +252,37 @@ const SingleListIssue: React.FC<Props> = ({
|
||||
: findHowManyDaysLeft(issue.target_date) <= 3 && "text-orange-400"
|
||||
}`}
|
||||
>
|
||||
<CalendarDaysIcon className="h-4 w-4" />
|
||||
{issue.target_date ? renderShortNumericDateFormat(issue.target_date) : "N/A"}
|
||||
<CustomDatePicker
|
||||
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">
|
||||
<h5 className="mb-1 font-medium text-gray-900">Due date</h5>
|
||||
<div>{renderShortNumericDateFormat(issue.target_date ?? "")}</div>
|
||||
|
@ -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 CreateUpdateCycleModal from "components/project/cycles/create-update-cycle-modal";
|
||||
// ui
|
||||
import { Button, CustomMenu, Input, Loader } from "components/ui";
|
||||
import { Button, CustomDatePicker, CustomMenu, Input, Loader } from "components/ui";
|
||||
// icons
|
||||
import { XMarkIcon } from "@heroicons/react/24/outline";
|
||||
// helpers
|
||||
@ -289,20 +289,25 @@ export const IssueForm: FC<IssueFormProps> = ({
|
||||
<IssueLabelSelect value={value} onChange={onChange} projectId={projectId} />
|
||||
)}
|
||||
/>
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="target_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<input
|
||||
type="date"
|
||||
value={value ?? ""}
|
||||
onChange={(e: any) => {
|
||||
onChange(e.target.value);
|
||||
<CustomDatePicker
|
||||
value={value}
|
||||
onChange={(val: Date) => {
|
||||
onChange(
|
||||
val
|
||||
? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
|
||||
: null
|
||||
);
|
||||
}}
|
||||
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"
|
||||
className="max-w-[7rem]"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<IssueParentSelect
|
||||
control={control}
|
||||
isOpen={parentIssueListModalOpen}
|
||||
|
@ -23,7 +23,7 @@ const BreakIntoModules: React.FC = () => (
|
||||
<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.
|
||||
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>
|
||||
|
@ -185,7 +185,7 @@ export const CreateProjectModal: React.FC<Props> = (props) => {
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex gap-3">
|
||||
<div className="flex-shrink-0">
|
||||
<label htmlFor="icon" className="mb-2 text-gray-500">
|
||||
Icon
|
||||
|
@ -1,18 +1,19 @@
|
||||
import React, { useEffect } from "react";
|
||||
// next
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
// swr
|
||||
|
||||
import { mutate } from "swr";
|
||||
|
||||
// react hook form
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
// headless
|
||||
// headless ui
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// services
|
||||
import cycleService from "services/cycles.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { Button, Input, TextArea, CustomSelect } from "components/ui";
|
||||
import { Button, Input, TextArea, CustomSelect, CustomDatePicker } from "components/ui";
|
||||
// common
|
||||
import { renderDateFormat } from "helpers/date-time.helper";
|
||||
// types
|
||||
@ -31,8 +32,8 @@ const defaultValues: Partial<ICycle> = {
|
||||
name: "",
|
||||
description: "",
|
||||
status: "draft",
|
||||
start_date: new Date().toString(),
|
||||
end_date: new Date().toString(),
|
||||
start_date: null,
|
||||
end_date: null,
|
||||
};
|
||||
|
||||
const CreateUpdateCycleModal: React.FC<Props> = ({ isOpen, setIsOpen, data, projectId }) => {
|
||||
@ -202,32 +203,62 @@ const CreateUpdateCycleModal: React.FC<Props> = ({ isOpen, setIsOpen, data, proj
|
||||
</div>
|
||||
<div className="flex gap-x-2">
|
||||
<div className="w-full">
|
||||
<Input
|
||||
id="start_date"
|
||||
label="Start Date"
|
||||
<h6 className="text-gray-500">Start Date</h6>
|
||||
<div className="w-full">
|
||||
<Controller
|
||||
control={control}
|
||||
name="start_date"
|
||||
type="date"
|
||||
placeholder="Enter start date"
|
||||
error={errors.start_date}
|
||||
register={register}
|
||||
validations={{
|
||||
required: "Start date is required",
|
||||
rules={{ required: "Start date is required" }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<CustomDatePicker
|
||||
renderAs="input"
|
||||
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 className="w-full">
|
||||
<Input
|
||||
id="end_date"
|
||||
label="End Date"
|
||||
<h6 className="text-gray-500">End Date</h6>
|
||||
<div className="w-full">
|
||||
<Controller
|
||||
control={control}
|
||||
name="end_date"
|
||||
type="date"
|
||||
placeholder="Enter end date"
|
||||
error={errors.end_date}
|
||||
register={register}
|
||||
validations={{
|
||||
required: "End date is required",
|
||||
rules={{ required: "End date is required" }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<CustomDatePicker
|
||||
renderAs="input"
|
||||
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>
|
||||
|
@ -13,7 +13,7 @@ import cyclesService from "services/cycles.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { Loader } from "components/ui";
|
||||
import { Loader, CustomDatePicker } from "components/ui";
|
||||
//progress-bar
|
||||
import { CircularProgressbar } from "react-circular-progressbar";
|
||||
import "react-circular-progressbar/dist/styles.css";
|
||||
@ -23,7 +23,7 @@ import { groupBy } from "helpers/array.helper";
|
||||
// types
|
||||
import { CycleIssueResponse, ICycle } from "types";
|
||||
// fetch-keys
|
||||
import { CYCLE_DETAIL } from "constants/fetch-keys";
|
||||
import { CYCLE_LIST } from "constants/fetch-keys";
|
||||
|
||||
type Props = {
|
||||
cycle: ICycle | undefined;
|
||||
@ -38,9 +38,7 @@ const defaultValues: Partial<ICycle> = {
|
||||
|
||||
const CycleDetailSidebar: React.FC<Props> = ({ cycle, isOpen, cycleIssues }) => {
|
||||
const router = useRouter();
|
||||
const {
|
||||
query: { workspaceSlug, projectId },
|
||||
} = router;
|
||||
const { workspaceSlug, projectId, cycleId } = router.query;
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@ -60,11 +58,21 @@ const CycleDetailSidebar: React.FC<Props> = ({ cycle, isOpen, cycleIssues }) =>
|
||||
const submitChanges = (data: Partial<ICycle>) => {
|
||||
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
|
||||
.patchCycle(workspaceSlug as string, projectId as string, cycle?.id ?? "", data)
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
mutate(CYCLE_DETAIL);
|
||||
mutate(CYCLE_LIST(projectId as string));
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
@ -160,16 +168,17 @@ const CycleDetailSidebar: React.FC<Props> = ({ cycle, isOpen, cycleIssues }) =>
|
||||
<Controller
|
||||
control={control}
|
||||
name="start_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<input
|
||||
type="date"
|
||||
id="cycleStartDate"
|
||||
value={value ?? ""}
|
||||
onChange={(e: any) => {
|
||||
submitChanges({ start_date: e.target.value });
|
||||
onChange(e.target.value);
|
||||
render={({ field: { value } }) => (
|
||||
<CustomDatePicker
|
||||
value={value}
|
||||
onChange={(val: Date) => {
|
||||
submitChanges({
|
||||
start_date: val
|
||||
? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
|
||||
: 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
|
||||
control={control}
|
||||
name="end_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<input
|
||||
type="date"
|
||||
id="moduleEndDate"
|
||||
value={value ?? ""}
|
||||
onChange={(e: any) => {
|
||||
submitChanges({ end_date: e.target.value });
|
||||
onChange(e.target.value);
|
||||
render={({ field: { value } }) => (
|
||||
<CustomDatePicker
|
||||
value={value}
|
||||
onChange={(val: Date) => {
|
||||
submitChanges({
|
||||
end_date: val
|
||||
? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
|
||||
: 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}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
@ -68,7 +68,8 @@ const BoardView: React.FC<Props> = ({ issues, handleDeleteIssue, userAuth }) =>
|
||||
|
||||
const handleOnDragEnd = useCallback(
|
||||
(result: DropResult) => {
|
||||
if (!result.destination) return;
|
||||
if (!result.destination || !workspaceSlug || !projectId) return;
|
||||
|
||||
const { source, destination, type } = result;
|
||||
|
||||
if (destination.droppableId === "trashBox") {
|
||||
@ -94,7 +95,7 @@ const BoardView: React.FC<Props> = ({ issues, handleDeleteIssue, userAuth }) =>
|
||||
newStates[destination.index].sequence = sequenceNumber;
|
||||
|
||||
mutateState(newStates, false);
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
stateServices
|
||||
.patchState(
|
||||
workspaceSlug as string,
|
||||
@ -140,18 +141,6 @@ const BoardView: React.FC<Props> = ({ issues, handleDeleteIssue, userAuth }) =>
|
||||
draggedItem.state = destinationStateId;
|
||||
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>(
|
||||
PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string),
|
||||
(prevData) => {
|
||||
@ -175,6 +164,15 @@ const BoardView: React.FC<Props> = ({ issues, handleDeleteIssue, userAuth }) =>
|
||||
},
|
||||
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));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,11 +76,6 @@ const activityDetails: {
|
||||
},
|
||||
};
|
||||
|
||||
const defaultValues: Partial<IIssueComment> = {
|
||||
comment_html: "",
|
||||
comment_json: "",
|
||||
};
|
||||
|
||||
const IssueActivitySection: React.FC<{
|
||||
issueActivities: IIssueActivity[];
|
||||
mutate: KeyedMutator<IIssueActivity[]>;
|
||||
@ -99,7 +94,7 @@ const IssueActivitySection: React.FC<{
|
||||
comment.id,
|
||||
comment
|
||||
)
|
||||
.then((response) => {
|
||||
.then((res) => {
|
||||
mutate();
|
||||
});
|
||||
};
|
||||
@ -180,6 +175,10 @@ const IssueActivitySection: React.FC<{
|
||||
? activity.new_value !== ""
|
||||
? "marked this issue being blocked by"
|
||||
: "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]
|
||||
?.message}{" "}
|
||||
</span>
|
||||
@ -203,7 +202,9 @@ const IssueActivitySection: React.FC<{
|
||||
) : activity.field === "assignee" ? (
|
||||
activity.old_value
|
||||
) : activity.field === "target_date" ? (
|
||||
activity.new_value ? (
|
||||
renderShortNumericDateFormat(activity.new_value as string)
|
||||
) : null
|
||||
) : activity.field === "description" ? (
|
||||
""
|
||||
) : (
|
||||
|
@ -4,20 +4,12 @@ import { useRouter } from "next/router";
|
||||
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
// react-hook-form
|
||||
import { useForm, Controller, UseFormWatch, Control } from "react-hook-form";
|
||||
|
||||
// react-color
|
||||
import { TwitterPicker } from "react-color";
|
||||
// services
|
||||
// headless ui
|
||||
import { Popover, Listbox, Transition } from "@headlessui/react";
|
||||
import {
|
||||
TagIcon,
|
||||
ChevronDownIcon,
|
||||
LinkIcon,
|
||||
CalendarDaysIcon,
|
||||
TrashIcon,
|
||||
PlusIcon,
|
||||
XMarkIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// 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 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";
|
||||
// headless ui
|
||||
// ui
|
||||
import { Input, Button, Spinner } from "components/ui";
|
||||
import { Input, Button, Spinner, CustomDatePicker } from "components/ui";
|
||||
import DatePicker from "react-datepicker";
|
||||
// icons
|
||||
import {
|
||||
TagIcon,
|
||||
ChevronDownIcon,
|
||||
LinkIcon,
|
||||
CalendarDaysIcon,
|
||||
TrashIcon,
|
||||
PlusIcon,
|
||||
XMarkIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
// helpers
|
||||
import { copyTextToClipboard } from "helpers/string.helper";
|
||||
// types
|
||||
@ -240,16 +240,16 @@ const IssueDetailSidebar: React.FC<Props> = ({
|
||||
<Controller
|
||||
control={control}
|
||||
name="target_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<input
|
||||
type="date"
|
||||
id="issueDate"
|
||||
value={value ?? ""}
|
||||
onChange={(e: any) => {
|
||||
submitChanges({ target_date: e.target.value });
|
||||
onChange(e.target.value);
|
||||
render={({ field: { value } }) => (
|
||||
<CustomDatePicker
|
||||
value={value}
|
||||
onChange={(val: Date) => {
|
||||
submitChanges({
|
||||
target_date: val
|
||||
? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
|
||||
: 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"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
@ -5,7 +5,7 @@ import { useRouter } from "next/router";
|
||||
import { mutate } from "swr";
|
||||
|
||||
// react-hook-form
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
// headless ui
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// 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 SelectStatus from "components/project/modules/create-update-module-modal/select-status";
|
||||
// ui
|
||||
import { Button, Input, TextArea } from "components/ui";
|
||||
import { Button, CustomDatePicker, Input, TextArea } from "components/ui";
|
||||
// services
|
||||
import modulesService from "services/modules.service";
|
||||
// hooks
|
||||
@ -193,32 +193,62 @@ const CreateUpdateModuleModal: React.FC<Props> = ({ isOpen, setIsOpen, data, pro
|
||||
</div>
|
||||
<div className="flex gap-x-2">
|
||||
<div className="w-full">
|
||||
<Input
|
||||
id="start_date"
|
||||
label="Start Date"
|
||||
<h6 className="text-gray-500">Start Date</h6>
|
||||
<div className="w-full">
|
||||
<Controller
|
||||
control={control}
|
||||
name="start_date"
|
||||
type="date"
|
||||
placeholder="Enter start date"
|
||||
error={errors.start_date}
|
||||
register={register}
|
||||
validations={{
|
||||
required: "Start date is required",
|
||||
rules={{ required: "Start date is required" }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<CustomDatePicker
|
||||
renderAs="input"
|
||||
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 className="w-full">
|
||||
<Input
|
||||
id="target_date"
|
||||
label="Target Date"
|
||||
<h6 className="text-gray-500">Target Date</h6>
|
||||
<div className="w-full">
|
||||
<Controller
|
||||
control={control}
|
||||
name="target_date"
|
||||
type="date"
|
||||
placeholder="Enter target date"
|
||||
error={errors.target_date}
|
||||
register={register}
|
||||
validations={{
|
||||
required: "Target date is required",
|
||||
rules={{ required: "Target date is required" }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<CustomDatePicker
|
||||
renderAs="input"
|
||||
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 className="flex flex-wrap items-center gap-2">
|
||||
|
@ -26,7 +26,7 @@ import ModuleLinkModal from "components/project/modules/module-link-modal";
|
||||
import { CircularProgressbar } from "react-circular-progressbar";
|
||||
import "react-circular-progressbar/dist/styles.css";
|
||||
// ui
|
||||
import { Loader } from "components/ui";
|
||||
import { CustomDatePicker, Loader } from "components/ui";
|
||||
// icons
|
||||
// helpers
|
||||
import { timeAgo } from "helpers/date-time.helper";
|
||||
@ -39,8 +39,8 @@ import { MODULE_LIST } from "constants/fetch-keys";
|
||||
|
||||
const defaultValues: Partial<IModule> = {
|
||||
members_list: [],
|
||||
start_date: new Date().toString(),
|
||||
target_date: new Date().toString(),
|
||||
start_date: null,
|
||||
target_date: null,
|
||||
status: null,
|
||||
};
|
||||
|
||||
@ -88,16 +88,21 @@ const ModuleDetailSidebar: React.FC<Props> = ({
|
||||
const submitChanges = (data: Partial<IModule>) => {
|
||||
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
|
||||
.patchModule(workspaceSlug as string, projectId as string, module.id, data)
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
mutate<IModule[]>(projectId && MODULE_LIST(projectId as string), (prevData) =>
|
||||
(prevData ?? []).map((module) => {
|
||||
if (module.id === moduleId) return { ...module, ...data };
|
||||
return module;
|
||||
})
|
||||
);
|
||||
mutate(MODULE_LIST(projectId as string));
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
@ -186,16 +191,16 @@ const ModuleDetailSidebar: React.FC<Props> = ({
|
||||
<Controller
|
||||
control={control}
|
||||
name="start_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<input
|
||||
type="date"
|
||||
id="moduleStartDate"
|
||||
value={value ?? ""}
|
||||
onChange={(e: any) => {
|
||||
submitChanges({ start_date: e.target.value });
|
||||
onChange(e.target.value);
|
||||
render={({ field: { value } }) => (
|
||||
<CustomDatePicker
|
||||
value={value}
|
||||
onChange={(val: Date) => {
|
||||
submitChanges({
|
||||
start_date: val
|
||||
? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
|
||||
: 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
|
||||
control={control}
|
||||
name="target_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<input
|
||||
type="date"
|
||||
id="moduleTargetDate"
|
||||
value={value ?? ""}
|
||||
onChange={(e: any) => {
|
||||
submitChanges({ target_date: e.target.value });
|
||||
onChange(e.target.value);
|
||||
render={({ field: { value } }) => (
|
||||
<CustomDatePicker
|
||||
value={value}
|
||||
onChange={(val: Date) => {
|
||||
submitChanges({
|
||||
target_date: val
|
||||
? `${val.getFullYear()}-${val.getMonth() + 1}-${val.getDate()}`
|
||||
: 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"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
@ -12,9 +12,7 @@ import { UserGroupIcon } from "@heroicons/react/24/outline";
|
||||
import workspaceService from "services/workspace.service";
|
||||
// headless ui
|
||||
// ui
|
||||
import { Spinner } from "components/ui";
|
||||
// icons
|
||||
import User from "public/user.png";
|
||||
import { AssigneesList, Spinner } from "components/ui";
|
||||
// types
|
||||
import { IModule } from "types";
|
||||
// constants
|
||||
@ -64,52 +62,7 @@ const SelectMembers: React.FC<Props> = ({ control, submitChanges }) => {
|
||||
>
|
||||
<div className="flex cursor-pointer items-center gap-1 text-xs">
|
||||
{value && Array.isArray(value) ? (
|
||||
<>
|
||||
{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>
|
||||
)}
|
||||
</>
|
||||
<AssigneesList userIds={value} length={10} />
|
||||
) : null}
|
||||
</div>
|
||||
</span>
|
||||
|
40
apps/app/components/ui/datepicker.tsx
Normal file
40
apps/app/components/ui/datepicker.tsx
Normal 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}
|
||||
/>
|
||||
);
|
@ -13,3 +13,4 @@ export * from "./spinner";
|
||||
export * from "./text-area";
|
||||
export * from "./tooltip";
|
||||
export * from "./avatar";
|
||||
export * from "./datepicker";
|
||||
|
@ -50,7 +50,7 @@ const SingleCycle: React.FC<UserAuth> = (props) => {
|
||||
const [selectedIssues, setSelectedIssues] = useState<SelectIssue>();
|
||||
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
||||
const [deleteIssue, setDeleteIssue] = useState<string | undefined>(undefined);
|
||||
const [cycleSidebar, setCycleSidebar] = useState(false);
|
||||
const [cycleSidebar, setCycleSidebar] = useState(true);
|
||||
|
||||
const [preloadedData, setPreloadedData] = useState<
|
||||
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | null
|
||||
|
@ -71,3 +71,23 @@
|
||||
border: 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 */
|
||||
|
Loading…
Reference in New Issue
Block a user