chore: select start date option for issue (#1813)

This commit is contained in:
Aaryan Khandelwal 2023-08-09 15:17:32 +05:30 committed by GitHub
parent 5f1209f1db
commit 4fcd081d27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 243 additions and 30 deletions

View File

@ -428,6 +428,40 @@ const activityDetails: {
), ),
icon: <Icon iconName="signal_cellular_alt" className="!text-sm" aria-hidden="true" />, 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: { state: {
message: (activity, showIssue) => ( message: (activity, showIssue) => (
<> <>

View File

@ -24,6 +24,7 @@ import {
ViewEstimateSelect, ViewEstimateSelect,
ViewIssueLabel, ViewIssueLabel,
ViewPrioritySelect, ViewPrioritySelect,
ViewStartDateSelect,
ViewStateSelect, ViewStateSelect,
} from "components/issues"; } from "components/issues";
// ui // ui
@ -322,6 +323,14 @@ export const SingleBoardIssue: React.FC<Props> = ({
selfPositioned selfPositioned
/> />
)} )}
{properties.start_date && issue.start_date && (
<ViewStartDateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
user={user}
isNotAllowed={isNotAllowed}
/>
)}
{properties.due_date && issue.target_date && ( {properties.due_date && issue.target_date && (
<ViewDueDateSelect <ViewDueDateSelect
issue={issue} issue={issue}

View File

@ -21,6 +21,7 @@ import {
ViewEstimateSelect, ViewEstimateSelect,
ViewLabelSelect, ViewLabelSelect,
ViewPrioritySelect, ViewPrioritySelect,
ViewStartDateSelect,
ViewStateSelect, ViewStateSelect,
} from "components/issues"; } from "components/issues";
// icons // icons
@ -230,7 +231,14 @@ export const SingleCalendarIssue: React.FC<Props> = ({
user={user} user={user}
/> />
)} )}
{properties.start_date && issue.start_date && (
<ViewStartDateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
user={user}
isNotAllowed={isNotAllowed}
/>
)}
{properties.due_date && issue.target_date && ( {properties.due_date && issue.target_date && (
<ViewDueDateSelect <ViewDueDateSelect
issue={issue} issue={issue}

View File

@ -16,6 +16,7 @@ import {
ViewEstimateSelect, ViewEstimateSelect,
ViewIssueLabel, ViewIssueLabel,
ViewPrioritySelect, ViewPrioritySelect,
ViewStartDateSelect,
ViewStateSelect, ViewStateSelect,
} from "components/issues"; } from "components/issues";
// ui // ui
@ -244,6 +245,14 @@ export const SingleListIssue: React.FC<Props> = ({
isNotAllowed={isNotAllowed} isNotAllowed={isNotAllowed}
/> />
)} )}
{properties.start_date && issue.start_date && (
<ViewStartDateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
user={user}
isNotAllowed={isNotAllowed}
/>
)}
{properties.due_date && issue.target_date && ( {properties.due_date && issue.target_date && (
<ViewDueDateSelect <ViewDueDateSelect
issue={issue} issue={issue}

View File

@ -75,6 +75,7 @@ const defaultValues: Partial<IIssue> = {
assignees_list: [], assignees_list: [],
labels: [], labels: [],
labels_list: [], labels_list: [],
start_date: null,
target_date: null, target_date: null,
}; };
@ -96,6 +97,7 @@ export interface IssueFormProps {
| "priority" | "priority"
| "assignee" | "assignee"
| "label" | "label"
| "startDate"
| "dueDate" | "dueDate"
| "estimate" | "estimate"
| "parent" | "parent"
@ -239,6 +241,15 @@ export const IssueForm: FC<IssueFormProps> = ({
}); });
}, [getValues, projectId, reset]); }, [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 ( return (
<> <>
{projectId && ( {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")) && ( {(fieldsToShow.includes("all") || fieldsToShow.includes("dueDate")) && (
<div> <div>
<Controller <Controller
control={control} control={control}
name="target_date" name="target_date"
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<IssueDateSelect value={value} onChange={onChange} /> <IssueDateSelect
label="Due date"
minDate={minDate ?? undefined}
onChange={onChange}
value={value}
/>
)} )}
/> />
</div> </div>

View File

@ -53,6 +53,7 @@ export interface IssuesModalProps {
| "priority" | "priority"
| "assignee" | "assignee"
| "label" | "label"
| "startDate"
| "dueDate" | "dueDate"
| "estimate" | "estimate"
| "parent" | "parent"

View File

@ -8,11 +8,14 @@ import DatePicker from "react-datepicker";
import { renderDateFormat, renderShortDateWithYearFormat } from "helpers/date-time.helper"; import { renderDateFormat, renderShortDateWithYearFormat } from "helpers/date-time.helper";
type Props = { type Props = {
value: string | null; label: string;
maxDate?: Date;
minDate?: Date;
onChange: (val: string | null) => void; 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"> <Popover className="relative flex items-center justify-center rounded-lg">
{({ open }) => ( {({ open }) => (
<> <>
@ -28,7 +31,7 @@ export const IssueDateSelect: React.FC<Props> = ({ value, onChange }) => (
) : ( ) : (
<> <>
<CalendarDaysIcon className="h-3.5 w-3.5 flex-shrink-0" /> <CalendarDaysIcon className="h-3.5 w-3.5 flex-shrink-0" />
<span>Due Date</span> <span>{label}</span>
</> </>
)} )}
</span> </span>
@ -51,6 +54,8 @@ export const IssueDateSelect: React.FC<Props> = ({ value, onChange }) => (
else onChange(renderDateFormat(val)); else onChange(renderDateFormat(val));
}} }}
dateFormat="dd-MM-yyyy" dateFormat="dd-MM-yyyy"
minDate={minDate}
maxDate={maxDate}
inline inline
/> />
</Popover.Panel> </Popover.Panel>

View File

@ -54,6 +54,7 @@ type Props = {
| "parent" | "parent"
| "blocker" | "blocker"
| "blocked" | "blocked"
| "startDate"
| "dueDate" | "dueDate"
| "cycle" | "cycle"
| "module" | "module"
@ -210,6 +211,15 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
fieldsToShow.includes("cycle") || fieldsToShow.includes("cycle") ||
fieldsToShow.includes("module"); 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; const isNotAllowed = memberRole.isGuest || memberRole.isViewer;
return ( return (
@ -367,6 +377,34 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
disabled={uneditable} 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")) && ( {(fieldsToShow.includes("all") || fieldsToShow.includes("dueDate")) && (
<div className="flex flex-wrap items-center py-2"> <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"> <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" className="bg-custom-background-90"
minDate={minDate ?? undefined}
disabled={isNotAllowed || uneditable} disabled={isNotAllowed || uneditable}
/> />
)} )}

View File

@ -32,9 +32,12 @@ export const ViewDueDateSelect: React.FC<Props> = ({
const { issueView } = useIssuesView(); const { issueView } = useIssuesView();
const minDate = issue.start_date ? new Date(issue.start_date) : null;
minDate?.setDate(minDate.getDate());
return ( return (
<Tooltip <Tooltip
tooltipHeading="Due Date" tooltipHeading="Due date"
tooltipContent={ tooltipContent={
issue.target_date ? renderShortDateWithYearFormat(issue.target_date) ?? "N/A" : "N/A" issue.target_date ? renderShortDateWithYearFormat(issue.target_date) ?? "N/A" : "N/A"
} }
@ -56,8 +59,6 @@ export const ViewDueDateSelect: React.FC<Props> = ({
partialUpdateIssue( partialUpdateIssue(
{ {
target_date: val, target_date: val,
priority: issue.priority,
state: issue.state,
}, },
issue issue
); );
@ -77,6 +78,7 @@ export const ViewDueDateSelect: React.FC<Props> = ({
className={`${issue?.target_date ? "w-[6.5rem]" : "w-[5rem] text-center"} ${ className={`${issue?.target_date ? "w-[6.5rem]" : "w-[5rem] text-center"} ${
issueView === "kanban" ? "bg-custom-background-90" : "bg-custom-background-100" issueView === "kanban" ? "bg-custom-background-90" : "bg-custom-background-100"
}`} }`}
minDate={minDate ?? undefined}
noBorder={noBorder} noBorder={noBorder}
disabled={isNotAllowed} disabled={isNotAllowed}
/> />

View File

@ -1,6 +1,7 @@
export * from "./assignee"; export * from "./assignee";
export * from "./due-date"; export * from "./due-date";
export * from "./estimate"; export * from "./estimate";
export * from "./priority";
export * from "./state";
export * from "./label"; export * from "./label";
export * from "./priority";
export * from "./start-date";
export * from "./state";

View 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>
);
};

View File

@ -15,6 +15,7 @@ type Props = {
className?: string; className?: string;
isClearable?: boolean; isClearable?: boolean;
disabled?: boolean; disabled?: boolean;
maxDate?: Date;
minDate?: Date; minDate?: Date;
}; };
@ -29,6 +30,7 @@ export const CustomDatePicker: React.FC<Props> = ({
className = "", className = "",
isClearable = true, isClearable = true,
disabled = false, disabled = false,
maxDate,
minDate, minDate,
}) => ( }) => (
<DatePicker <DatePicker
@ -54,6 +56,7 @@ export const CustomDatePicker: React.FC<Props> = ({
dateFormat="MMM dd, yyyy" dateFormat="MMM dd, yyyy"
isClearable={isClearable} isClearable={isClearable}
disabled={disabled} disabled={disabled}
maxDate={maxDate}
minDate={minDate} minDate={minDate}
/> />
); );

View File

@ -11,6 +11,7 @@ import { IssuePriorities, Properties } from "types";
const initialValues: Properties = { const initialValues: Properties = {
assignee: true, assignee: true,
start_date: true,
due_date: true, due_date: true,
key: true, key: true,
labels: true, labels: true,
@ -91,6 +92,7 @@ const useIssuesProperties = (workspaceSlug?: string, projectId?: string) => {
const newProperties: Properties = { const newProperties: Properties = {
assignee: properties.assignee, assignee: properties.assignee,
start_date: properties.start_date,
due_date: properties.due_date, due_date: properties.due_date,
key: properties.key, key: properties.key,
labels: properties.labels, labels: properties.labels,

View File

@ -28,17 +28,18 @@ import { PROJECT_ISSUES_ACTIVITY, ISSUE_DETAILS } from "constants/fetch-keys";
import { truncateText } from "helpers/string.helper"; import { truncateText } from "helpers/string.helper";
const defaultValues = { const defaultValues = {
name: "", assignees_list: [],
description: "", description: "",
description_html: "", description_html: "",
estimate_point: null, estimate_point: null,
state: "",
assignees_list: [],
priority: "low",
target_date: new Date().toString(),
issue_cycle: null, issue_cycle: null,
issue_module: null, issue_module: null,
labels_list: [], labels_list: [],
name: "",
priority: "low",
start_date: null,
state: "",
target_date: null,
}; };
const IssueDetailsPage: NextPage = () => { const IssueDetailsPage: NextPage = () => {

View File

@ -10,6 +10,7 @@ import type {
IWorkspaceLite, IWorkspaceLite,
IStateLite, IStateLite,
TStateGroups, TStateGroups,
Properties,
} from "types"; } from "types";
export interface IIssueCycle { export interface IIssueCycle {
@ -147,21 +148,6 @@ export type IssuePriorities = {
user: string; 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 { export interface IIssueLabels {
id: string; id: string;
created_at: Date; created_at: Date;

View File

@ -49,6 +49,7 @@ export interface IWorkspaceBulkInviteFormData {
export type Properties = { export type Properties = {
assignee: boolean; assignee: boolean;
start_date: boolean;
due_date: boolean; due_date: boolean;
labels: boolean; labels: boolean;
key: boolean; key: boolean;