forked from github/plane
chore: select start date option for issue (#1813)
This commit is contained in:
parent
5f1209f1db
commit
4fcd081d27
@ -428,6 +428,40 @@ const activityDetails: {
|
||||
),
|
||||
icon: <Icon iconName="signal_cellular_alt" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
start_date: {
|
||||
message: (activity, showIssue) => {
|
||||
if (!activity.new_value)
|
||||
return (
|
||||
<>
|
||||
removed the start date
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
from <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<>
|
||||
set the start date to{" "}
|
||||
<span className="font-medium text-custom-text-100">
|
||||
{renderShortDateWithYearFormat(activity.new_value)}
|
||||
</span>
|
||||
{showIssue && (
|
||||
<>
|
||||
{" "}
|
||||
for <IssueLink activity={activity} />
|
||||
</>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
);
|
||||
},
|
||||
icon: <Icon iconName="calendar_today" className="!text-sm" aria-hidden="true" />,
|
||||
},
|
||||
state: {
|
||||
message: (activity, showIssue) => (
|
||||
<>
|
||||
|
@ -24,6 +24,7 @@ import {
|
||||
ViewEstimateSelect,
|
||||
ViewIssueLabel,
|
||||
ViewPrioritySelect,
|
||||
ViewStartDateSelect,
|
||||
ViewStateSelect,
|
||||
} from "components/issues";
|
||||
// ui
|
||||
@ -322,6 +323,14 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
||||
selfPositioned
|
||||
/>
|
||||
)}
|
||||
{properties.start_date && issue.start_date && (
|
||||
<ViewStartDateSelect
|
||||
issue={issue}
|
||||
partialUpdateIssue={partialUpdateIssue}
|
||||
user={user}
|
||||
isNotAllowed={isNotAllowed}
|
||||
/>
|
||||
)}
|
||||
{properties.due_date && issue.target_date && (
|
||||
<ViewDueDateSelect
|
||||
issue={issue}
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
ViewEstimateSelect,
|
||||
ViewLabelSelect,
|
||||
ViewPrioritySelect,
|
||||
ViewStartDateSelect,
|
||||
ViewStateSelect,
|
||||
} from "components/issues";
|
||||
// icons
|
||||
@ -230,7 +231,14 @@ export const SingleCalendarIssue: React.FC<Props> = ({
|
||||
user={user}
|
||||
/>
|
||||
)}
|
||||
|
||||
{properties.start_date && issue.start_date && (
|
||||
<ViewStartDateSelect
|
||||
issue={issue}
|
||||
partialUpdateIssue={partialUpdateIssue}
|
||||
user={user}
|
||||
isNotAllowed={isNotAllowed}
|
||||
/>
|
||||
)}
|
||||
{properties.due_date && issue.target_date && (
|
||||
<ViewDueDateSelect
|
||||
issue={issue}
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
ViewEstimateSelect,
|
||||
ViewIssueLabel,
|
||||
ViewPrioritySelect,
|
||||
ViewStartDateSelect,
|
||||
ViewStateSelect,
|
||||
} from "components/issues";
|
||||
// ui
|
||||
@ -244,6 +245,14 @@ export const SingleListIssue: React.FC<Props> = ({
|
||||
isNotAllowed={isNotAllowed}
|
||||
/>
|
||||
)}
|
||||
{properties.start_date && issue.start_date && (
|
||||
<ViewStartDateSelect
|
||||
issue={issue}
|
||||
partialUpdateIssue={partialUpdateIssue}
|
||||
user={user}
|
||||
isNotAllowed={isNotAllowed}
|
||||
/>
|
||||
)}
|
||||
{properties.due_date && issue.target_date && (
|
||||
<ViewDueDateSelect
|
||||
issue={issue}
|
||||
|
@ -75,6 +75,7 @@ const defaultValues: Partial<IIssue> = {
|
||||
assignees_list: [],
|
||||
labels: [],
|
||||
labels_list: [],
|
||||
start_date: null,
|
||||
target_date: null,
|
||||
};
|
||||
|
||||
@ -96,6 +97,7 @@ export interface IssueFormProps {
|
||||
| "priority"
|
||||
| "assignee"
|
||||
| "label"
|
||||
| "startDate"
|
||||
| "dueDate"
|
||||
| "estimate"
|
||||
| "parent"
|
||||
@ -239,6 +241,15 @@ export const IssueForm: FC<IssueFormProps> = ({
|
||||
});
|
||||
}, [getValues, projectId, reset]);
|
||||
|
||||
const startDate = watch("start_date");
|
||||
const targetDate = watch("target_date");
|
||||
|
||||
const minDate = startDate ? new Date(startDate) : null;
|
||||
minDate?.setDate(minDate.getDate());
|
||||
|
||||
const maxDate = targetDate ? new Date(targetDate) : null;
|
||||
maxDate?.setDate(maxDate.getDate());
|
||||
|
||||
return (
|
||||
<>
|
||||
{projectId && (
|
||||
@ -447,13 +458,34 @@ export const IssueForm: FC<IssueFormProps> = ({
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("startDate")) && (
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="start_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueDateSelect
|
||||
label="Start date"
|
||||
maxDate={maxDate ?? undefined}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("dueDate")) && (
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="target_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueDateSelect value={value} onChange={onChange} />
|
||||
<IssueDateSelect
|
||||
label="Due date"
|
||||
minDate={minDate ?? undefined}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
@ -53,6 +53,7 @@ export interface IssuesModalProps {
|
||||
| "priority"
|
||||
| "assignee"
|
||||
| "label"
|
||||
| "startDate"
|
||||
| "dueDate"
|
||||
| "estimate"
|
||||
| "parent"
|
||||
|
@ -8,11 +8,14 @@ import DatePicker from "react-datepicker";
|
||||
import { renderDateFormat, renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
||||
|
||||
type Props = {
|
||||
value: string | null;
|
||||
label: string;
|
||||
maxDate?: Date;
|
||||
minDate?: Date;
|
||||
onChange: (val: string | null) => void;
|
||||
value: string | null;
|
||||
};
|
||||
|
||||
export const IssueDateSelect: React.FC<Props> = ({ value, onChange }) => (
|
||||
export const IssueDateSelect: React.FC<Props> = ({ label, maxDate, minDate, onChange, value }) => (
|
||||
<Popover className="relative flex items-center justify-center rounded-lg">
|
||||
{({ open }) => (
|
||||
<>
|
||||
@ -28,7 +31,7 @@ export const IssueDateSelect: React.FC<Props> = ({ value, onChange }) => (
|
||||
) : (
|
||||
<>
|
||||
<CalendarDaysIcon className="h-3.5 w-3.5 flex-shrink-0" />
|
||||
<span>Due Date</span>
|
||||
<span>{label}</span>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
@ -51,6 +54,8 @@ export const IssueDateSelect: React.FC<Props> = ({ value, onChange }) => (
|
||||
else onChange(renderDateFormat(val));
|
||||
}}
|
||||
dateFormat="dd-MM-yyyy"
|
||||
minDate={minDate}
|
||||
maxDate={maxDate}
|
||||
inline
|
||||
/>
|
||||
</Popover.Panel>
|
||||
|
@ -54,6 +54,7 @@ type Props = {
|
||||
| "parent"
|
||||
| "blocker"
|
||||
| "blocked"
|
||||
| "startDate"
|
||||
| "dueDate"
|
||||
| "cycle"
|
||||
| "module"
|
||||
@ -210,6 +211,15 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
||||
fieldsToShow.includes("cycle") ||
|
||||
fieldsToShow.includes("module");
|
||||
|
||||
const startDate = watchIssue("start_date");
|
||||
const targetDate = watchIssue("target_date");
|
||||
|
||||
const minDate = startDate ? new Date(startDate) : null;
|
||||
minDate?.setDate(minDate.getDate());
|
||||
|
||||
const maxDate = targetDate ? new Date(targetDate) : null;
|
||||
maxDate?.setDate(maxDate.getDate());
|
||||
|
||||
const isNotAllowed = memberRole.isGuest || memberRole.isViewer;
|
||||
|
||||
return (
|
||||
@ -367,6 +377,34 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
||||
disabled={uneditable}
|
||||
/>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("startDate")) && (
|
||||
<div className="flex flex-wrap items-center py-2">
|
||||
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 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
|
||||
placeholder="Start date"
|
||||
value={value}
|
||||
onChange={(val) =>
|
||||
submitChanges({
|
||||
start_date: val,
|
||||
})
|
||||
}
|
||||
className="bg-custom-background-90"
|
||||
maxDate={maxDate ?? undefined}
|
||||
disabled={isNotAllowed || uneditable}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("dueDate")) && (
|
||||
<div className="flex flex-wrap items-center py-2">
|
||||
<div className="flex items-center gap-x-2 text-sm text-custom-text-200 sm:basis-1/2">
|
||||
@ -387,6 +425,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
||||
})
|
||||
}
|
||||
className="bg-custom-background-90"
|
||||
minDate={minDate ?? undefined}
|
||||
disabled={isNotAllowed || uneditable}
|
||||
/>
|
||||
)}
|
||||
|
@ -32,9 +32,12 @@ export const ViewDueDateSelect: React.FC<Props> = ({
|
||||
|
||||
const { issueView } = useIssuesView();
|
||||
|
||||
const minDate = issue.start_date ? new Date(issue.start_date) : null;
|
||||
minDate?.setDate(minDate.getDate());
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
tooltipHeading="Due Date"
|
||||
tooltipHeading="Due date"
|
||||
tooltipContent={
|
||||
issue.target_date ? renderShortDateWithYearFormat(issue.target_date) ?? "N/A" : "N/A"
|
||||
}
|
||||
@ -56,8 +59,6 @@ export const ViewDueDateSelect: React.FC<Props> = ({
|
||||
partialUpdateIssue(
|
||||
{
|
||||
target_date: val,
|
||||
priority: issue.priority,
|
||||
state: issue.state,
|
||||
},
|
||||
issue
|
||||
);
|
||||
@ -77,6 +78,7 @@ export const ViewDueDateSelect: React.FC<Props> = ({
|
||||
className={`${issue?.target_date ? "w-[6.5rem]" : "w-[5rem] text-center"} ${
|
||||
issueView === "kanban" ? "bg-custom-background-90" : "bg-custom-background-100"
|
||||
}`}
|
||||
minDate={minDate ?? undefined}
|
||||
noBorder={noBorder}
|
||||
disabled={isNotAllowed}
|
||||
/>
|
||||
|
@ -1,6 +1,7 @@
|
||||
export * from "./assignee";
|
||||
export * from "./due-date";
|
||||
export * from "./estimate";
|
||||
export * from "./priority";
|
||||
export * from "./state";
|
||||
export * from "./label";
|
||||
export * from "./priority";
|
||||
export * from "./start-date";
|
||||
export * from "./state";
|
||||
|
80
apps/app/components/issues/view-select/start-date.tsx
Normal file
80
apps/app/components/issues/view-select/start-date.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// ui
|
||||
import { CustomDatePicker, Tooltip } from "components/ui";
|
||||
// helpers
|
||||
import { findHowManyDaysLeft, renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
||||
// services
|
||||
import trackEventServices from "services/track-event.service";
|
||||
// types
|
||||
import { ICurrentUserResponse, IIssue } from "types";
|
||||
import useIssuesView from "hooks/use-issues-view";
|
||||
|
||||
type Props = {
|
||||
issue: IIssue;
|
||||
partialUpdateIssue: (formData: Partial<IIssue>, issue: IIssue) => void;
|
||||
tooltipPosition?: "top" | "bottom";
|
||||
noBorder?: boolean;
|
||||
user: ICurrentUserResponse | undefined;
|
||||
isNotAllowed: boolean;
|
||||
};
|
||||
|
||||
export const ViewStartDateSelect: React.FC<Props> = ({
|
||||
issue,
|
||||
partialUpdateIssue,
|
||||
tooltipPosition = "top",
|
||||
noBorder = false,
|
||||
user,
|
||||
isNotAllowed,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { issueView } = useIssuesView();
|
||||
|
||||
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
||||
maxDate?.setDate(maxDate.getDate());
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
tooltipHeading="Start date"
|
||||
tooltipContent={
|
||||
issue.start_date ? renderShortDateWithYearFormat(issue.start_date) ?? "N/A" : "N/A"
|
||||
}
|
||||
position={tooltipPosition}
|
||||
>
|
||||
<div className="group flex-shrink-0 relative max-w-[6.5rem]">
|
||||
<CustomDatePicker
|
||||
placeholder="Due date"
|
||||
value={issue?.start_date}
|
||||
onChange={(val) => {
|
||||
partialUpdateIssue(
|
||||
{
|
||||
start_date: val,
|
||||
},
|
||||
issue
|
||||
);
|
||||
trackEventServices.trackIssuePartialPropertyUpdateEvent(
|
||||
{
|
||||
workspaceSlug,
|
||||
workspaceId: issue.workspace,
|
||||
projectId: issue.project_detail.id,
|
||||
projectIdentifier: issue.project_detail.identifier,
|
||||
projectName: issue.project_detail.name,
|
||||
issueId: issue.id,
|
||||
},
|
||||
"ISSUE_PROPERTY_UPDATE_DUE_DATE",
|
||||
user
|
||||
);
|
||||
}}
|
||||
className={`${issue?.start_date ? "w-[6.5rem]" : "w-[5rem] text-center"} ${
|
||||
issueView === "kanban" ? "bg-custom-background-90" : "bg-custom-background-100"
|
||||
}`}
|
||||
maxDate={maxDate ?? undefined}
|
||||
noBorder={noBorder}
|
||||
disabled={isNotAllowed}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
@ -15,6 +15,7 @@ type Props = {
|
||||
className?: string;
|
||||
isClearable?: boolean;
|
||||
disabled?: boolean;
|
||||
maxDate?: Date;
|
||||
minDate?: Date;
|
||||
};
|
||||
|
||||
@ -29,6 +30,7 @@ export const CustomDatePicker: React.FC<Props> = ({
|
||||
className = "",
|
||||
isClearable = true,
|
||||
disabled = false,
|
||||
maxDate,
|
||||
minDate,
|
||||
}) => (
|
||||
<DatePicker
|
||||
@ -54,6 +56,7 @@ export const CustomDatePicker: React.FC<Props> = ({
|
||||
dateFormat="MMM dd, yyyy"
|
||||
isClearable={isClearable}
|
||||
disabled={disabled}
|
||||
maxDate={maxDate}
|
||||
minDate={minDate}
|
||||
/>
|
||||
);
|
||||
|
@ -11,6 +11,7 @@ import { IssuePriorities, Properties } from "types";
|
||||
|
||||
const initialValues: Properties = {
|
||||
assignee: true,
|
||||
start_date: true,
|
||||
due_date: true,
|
||||
key: true,
|
||||
labels: true,
|
||||
@ -91,6 +92,7 @@ const useIssuesProperties = (workspaceSlug?: string, projectId?: string) => {
|
||||
|
||||
const newProperties: Properties = {
|
||||
assignee: properties.assignee,
|
||||
start_date: properties.start_date,
|
||||
due_date: properties.due_date,
|
||||
key: properties.key,
|
||||
labels: properties.labels,
|
||||
|
@ -28,17 +28,18 @@ import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS } from "constants/fetch-keys";
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
|
||||
const defaultValues = {
|
||||
name: "",
|
||||
assignees_list: [],
|
||||
description: "",
|
||||
description_html: "",
|
||||
estimate_point: null,
|
||||
state: "",
|
||||
assignees_list: [],
|
||||
priority: "low",
|
||||
target_date: new Date().toString(),
|
||||
issue_cycle: null,
|
||||
issue_module: null,
|
||||
labels_list: [],
|
||||
name: "",
|
||||
priority: "low",
|
||||
start_date: null,
|
||||
state: "",
|
||||
target_date: null,
|
||||
};
|
||||
|
||||
const IssueDetailsPage: NextPage = () => {
|
||||
|
16
apps/app/types/issues.d.ts
vendored
16
apps/app/types/issues.d.ts
vendored
@ -10,6 +10,7 @@ import type {
|
||||
IWorkspaceLite,
|
||||
IStateLite,
|
||||
TStateGroups,
|
||||
Properties,
|
||||
} from "types";
|
||||
|
||||
export interface IIssueCycle {
|
||||
@ -147,21 +148,6 @@ export type IssuePriorities = {
|
||||
user: string;
|
||||
};
|
||||
|
||||
export type Properties = {
|
||||
assignee: boolean;
|
||||
due_date: boolean;
|
||||
labels: boolean;
|
||||
key: boolean;
|
||||
priority: boolean;
|
||||
state: boolean;
|
||||
sub_issue_count: boolean;
|
||||
link: boolean;
|
||||
attachment_count: boolean;
|
||||
estimate: boolean;
|
||||
created_on: boolean;
|
||||
updated_on: boolean;
|
||||
};
|
||||
|
||||
export interface IIssueLabels {
|
||||
id: string;
|
||||
created_at: Date;
|
||||
|
1
apps/app/types/workspace.d.ts
vendored
1
apps/app/types/workspace.d.ts
vendored
@ -49,6 +49,7 @@ export interface IWorkspaceBulkInviteFormData {
|
||||
|
||||
export type Properties = {
|
||||
assignee: boolean;
|
||||
start_date: boolean;
|
||||
due_date: boolean;
|
||||
labels: boolean;
|
||||
key: boolean;
|
||||
|
Loading…
Reference in New Issue
Block a user