chore: issue update status in peekview & detail (#2985)

This commit is contained in:
Lakhan Baheti 2023-12-05 14:40:29 +05:30 committed by sriram veeraghanta
parent d12bd9507e
commit 1b51892489
8 changed files with 161 additions and 60 deletions

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect } from "react";
import { useCallback, useEffect, useState } from "react";
import Router, { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
@ -8,10 +8,10 @@ import { AlertTriangle, CheckCircle2, Clock, Copy, ExternalLink, Inbox, XCircle
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { IssueDescriptionForm, IssueDetailsSidebar, IssueReaction } from "components/issues";
import { IssueDescriptionForm, IssueDetailsSidebar, IssueReaction, IssueUpdateStatus } from "components/issues";
import { InboxIssueActivity } from "components/inbox";
// ui
import { Loader } from "@plane/ui";
import { Loader, StateGroupIcon } from "@plane/ui";
// helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
// types
@ -31,7 +31,15 @@ export const InboxMainContent: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId, inboxId, inboxIssueId } = router.query;
const { inboxIssues: inboxIssuesStore, inboxIssueDetails: inboxIssueDetailsStore, user: userStore } = useMobxStore();
// states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
const {
inboxIssues: inboxIssuesStore,
inboxIssueDetails: inboxIssueDetailsStore,
user: userStore,
projectState: { states },
} = useMobxStore();
const user = userStore.currentUser;
const userRole = userStore.currentProjectRole;
@ -55,6 +63,9 @@ export const InboxMainContent: React.FC = observer(() => {
const issuesList = inboxId ? inboxIssuesStore.inboxIssues[inboxId.toString()] : undefined;
const issueDetails = inboxIssueId ? inboxIssueDetailsStore.issueDetails[inboxIssueId.toString()] : undefined;
const currentIssueState = projectId
? states[projectId.toString()]?.find((s) => s.id === issueDetails?.state)
: undefined;
const submitChanges = useCallback(
async (formData: Partial<IInboxIssue>) => {
@ -217,8 +228,20 @@ export const InboxMainContent: React.FC = observer(() => {
</>
) : null}
</div>
<div className="flex items-center mb-5">
{currentIssueState && (
<StateGroupIcon
className="h-4 w-4 mr-3"
stateGroup={currentIssueState.group}
color={currentIssueState.color}
/>
)}
<IssueUpdateStatus isSubmitting={isSubmitting} issueDetail={issueDetails} />
</div>
<div>
<IssueDescriptionForm
setIsSubmitting={(value) => setIsSubmitting(value)}
isSubmitting={isSubmitting}
workspaceSlug={workspaceSlug as string}
issue={{
name: issueDetails.name,

View File

@ -26,14 +26,15 @@ export interface IssueDetailsProps {
workspaceSlug: string;
handleFormSubmit: (value: IssueDescriptionFormValues) => Promise<void>;
isAllowed: boolean;
isSubmitting: "submitting" | "submitted" | "saved";
setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void;
}
const fileService = new FileService();
export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
const { issue, handleFormSubmit, workspaceSlug, isAllowed } = props;
const { issue, handleFormSubmit, workspaceSlug, isAllowed, isSubmitting, setIsSubmitting } = props;
// states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
const [characterLimit, setCharacterLimit] = useState(false);
const { setShowAlert } = useReloadConfirmations();
@ -166,13 +167,6 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
/>
)}
/>
<div
className={`absolute right-5 bottom-5 text-xs text-custom-text-200 border border-custom-border-400 rounded-xl w-[6.5rem] py-1 z-10 flex items-center justify-center ${
isSubmitting === "saved" ? "fadeOut" : "fadeIn"
}`}
>
{isSubmitting === "submitting" ? "Saving..." : "Saved"}
</div>
</div>
</div>
);

View File

@ -15,6 +15,7 @@ export * from "./sidebar";
export * from "./label";
export * from "./issue-reaction";
export * from "./confirm-issue-discard";
export * from "./issue-update-status";
// draft issue
export * from "./draft-issue-form";

View File

@ -26,16 +26,27 @@ interface IPeekOverviewIssueDetails {
issueUpdate: (issue: Partial<IIssue>) => void;
issueReactionCreate: (reaction: string) => void;
issueReactionRemove: (reaction: string) => void;
isSubmitting: "submitting" | "submitted" | "saved";
setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void;
}
export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) => {
const { workspaceSlug, issue, issueReactions, user, issueUpdate, issueReactionCreate, issueReactionRemove } = props;
const {
workspaceSlug,
issue,
issueReactions,
user,
issueUpdate,
issueReactionCreate,
issueReactionRemove,
isSubmitting,
setIsSubmitting,
} = props;
// store
const { user: userStore } = useMobxStore();
const { currentProjectRole } = userStore;
const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
// states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
const [characterLimit, setCharacterLimit] = useState(false);
// hooks
const { setShowAlert } = useReloadConfirmations();
@ -172,13 +183,6 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
/>
)}
/>
<div
className={`absolute right-5 bottom-5 text-xs text-custom-text-200 border border-custom-border-400 rounded-xl w-[6.5rem] py-1 z-10 flex items-center justify-center ${
isSubmitting === "saved" ? "fadeOut" : "fadeIn"
}`}
>
{isSubmitting === "submitting" ? "Saving..." : "Saved"}
</div>
</div>
<IssueReaction
issueReactions={issueReactions}

View File

@ -8,8 +8,7 @@ import { PeekOverviewIssueDetails } from "./issue-detail";
import { PeekOverviewProperties } from "./properties";
import { IssueComment } from "./activity";
import { Button, CenterPanelIcon, CustomSelect, FullScreenPanelIcon, SidePanelIcon, Spinner } from "@plane/ui";
import { DeleteIssueModal } from "../delete-issue-modal";
import { DeleteArchivedIssueModal } from "../delete-archived-issue-modal";
import { DeleteIssueModal, DeleteArchivedIssueModal, IssueUpdateStatus } from "components/issues/";
// types
import { IIssue } from "types";
// hooks
@ -93,6 +92,7 @@ export const IssueView: FC<IIssueView> = observer((props) => {
const [peekMode, setPeekMode] = useState<TPeekModes>("side-peek");
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
const updateRoutePeekId = () => {
if (issueId != peekIssueId) {
@ -216,33 +216,35 @@ export const IssueView: FC<IIssueView> = observer((props) => {
</div>
)}
</div>
<div className="flex items-center gap-4">
{issue?.created_by !== user?.id &&
!issue?.assignees.includes(user?.id ?? "") &&
!router.pathname.includes("[archivedIssueId]") && (
<Button
size="sm"
prependIcon={<Bell className="h-3 w-3" />}
variant="outline-primary"
className="hover:!bg-custom-primary-100/20"
onClick={() =>
issueSubscription && issueSubscription.subscribed
? issueSubscriptionRemove()
: issueSubscriptionCreate()
}
>
{issueSubscription && issueSubscription.subscribed ? "Unsubscribe" : "Subscribe"}
</Button>
)}
<button onClick={handleCopyText}>
<Link2 className="h-4 w-4 text-custom-text-400 hover:text-custom-text-200 -rotate-45" />
</button>
{!disableUserActions && (
<button onClick={() => setDeleteIssueModal(true)}>
<Trash2 className="h-4 w-4 text-custom-text-400 hover:text-custom-text-200" />
<div className="flex items-center gap-x-4">
<IssueUpdateStatus isSubmitting={isSubmitting} />
<div className="flex items-center gap-4">
{issue?.created_by !== user?.id &&
!issue?.assignees.includes(user?.id ?? "") &&
!router.pathname.includes("[archivedIssueId]") && (
<Button
size="sm"
prependIcon={<Bell className="h-3 w-3" />}
variant="outline-primary"
className="hover:!bg-custom-primary-100/20"
onClick={() =>
issueSubscription && issueSubscription.subscribed
? issueSubscriptionRemove()
: issueSubscriptionCreate()
}
>
{issueSubscription && issueSubscription.subscribed ? "Unsubscribe" : "Subscribe"}
</Button>
)}
<button onClick={handleCopyText}>
<Link2 className="h-4 w-4 text-custom-text-300 hover:text-custom-text-200 -rotate-45" />
</button>
)}
{!disableUserActions && (
<button onClick={() => setDeleteIssueModal(true)}>
<Trash2 className="h-4 w-4 text-custom-text-300 hover:text-custom-text-200" />
</button>
)}
</div>
</div>
</div>
@ -261,6 +263,8 @@ export const IssueView: FC<IIssueView> = observer((props) => {
<div className="absolute top-0 left-0 h-full min-h-full w-full z-[9] flex items-center justify-center bg-custom-background-100 opacity-60" />
)}
<PeekOverviewIssueDetails
setIsSubmitting={(value) => setIsSubmitting(value)}
isSubmitting={isSubmitting}
workspaceSlug={workspaceSlug}
issue={issue}
issueUpdate={issueUpdate}
@ -295,6 +299,8 @@ export const IssueView: FC<IIssueView> = observer((props) => {
<div className="relative w-full h-full space-y-6 p-4 py-5 overflow-auto">
<div className={isArchived ? "pointer-events-none" : ""}>
<PeekOverviewIssueDetails
setIsSubmitting={(value) => setIsSubmitting(value)}
isSubmitting={isSubmitting}
workspaceSlug={workspaceSlug}
issue={issue}
issueReactions={issueReactions}

View File

@ -0,0 +1,32 @@
import React from "react";
import { RefreshCw } from "lucide-react";
// types
import { IIssue } from "types";
type Props = {
isSubmitting: "submitting" | "submitted" | "saved";
issueDetail?: IIssue;
};
export const IssueUpdateStatus: React.FC<Props> = (props) => {
const { isSubmitting, issueDetail } = props;
return (
<>
{issueDetail && (
<h4 className="text-lg text-custom-text-300 font-medium mr-4">
{issueDetail.project_detail?.identifier}-{issueDetail.sequence_id}
</h4>
)}
<div
className={`flex transition-all duration-300 items-center gap-x-2 ${
isSubmitting === "saved" ? "fadeOut" : "fadeIn"
}`}
>
{isSubmitting !== "submitted" && isSubmitting !== "saved" && (
<RefreshCw className="h-4 w-4 stroke-custom-text-300" />
)}
<span className="text-sm text-custom-text-300">{isSubmitting === "submitting" ? "Saving..." : "Saved"}</span>
</div>
</>
);
};

View File

@ -2,6 +2,7 @@ import Link from "next/link";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import useSWR, { mutate } from "swr";
import { MinusCircle } from "lucide-react";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// services
@ -16,12 +17,12 @@ import {
IssueAttachments,
IssueDescriptionForm,
IssueReaction,
IssueUpdateStatus,
} from "components/issues";
import { useState } from "react";
import { SubIssuesRoot } from "./sub-issues";
// ui
import { CustomMenu, LayersIcon } from "@plane/ui";
// icons
import { MinusCircle } from "lucide-react";
import { CustomMenu, LayersIcon, StateGroupIcon } from "@plane/ui";
// types
import { IIssue, IIssueComment } from "types";
// fetch-keys
@ -41,15 +42,25 @@ const issueCommentService = new IssueCommentService();
export const IssueMainContent: React.FC<Props> = observer((props) => {
const { issueDetails, submitChanges, uneditable = false } = props;
// states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
const { setToastAlert } = useToast();
const { user: userStore, project: projectStore } = useMobxStore();
const {
user: userStore,
project: projectStore,
projectState: { states },
} = useMobxStore();
const user = userStore.currentUser ?? undefined;
const userRole = userStore.currentProjectRole;
const projectDetails = projectId ? projectStore.project_details[projectId.toString()] : undefined;
const currentIssueState = projectId
? states[projectId.toString()]?.find((s) => s.id === issueDetails.state)
: undefined;
const { data: siblingIssues } = useSWR(
workspaceSlug && projectId && issueDetails?.parent ? SUB_ISSUES(issueDetails.parent) : null,
@ -165,7 +176,19 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
</CustomMenu>
</div>
) : null}
<div className="flex items-center mb-5">
{currentIssueState && (
<StateGroupIcon
className="h-4 w-4 mr-3"
stateGroup={currentIssueState.group}
color={currentIssueState.color}
/>
)}
<IssueUpdateStatus isSubmitting={isSubmitting} issueDetail={issueDetails} />
</div>
<IssueDescriptionForm
setIsSubmitting={(value) => setIsSubmitting(value)}
isSubmitting={isSubmitting}
workspaceSlug={workspaceSlug as string}
issue={issueDetails}
handleFormSubmit={submitChanges}

View File

@ -33,7 +33,7 @@ import {
import { CustomDatePicker } from "components/ui";
// icons
import { Bell, CalendarDays, LinkIcon, Plus, Signal, Tag, Trash2, Triangle, User2 } from "lucide-react";
import { Button, ContrastIcon, DiceIcon, DoubleCircleIcon, UserGroupIcon } from "@plane/ui";
import { Button, ContrastIcon, DiceIcon, DoubleCircleIcon, StateGroupIcon, UserGroupIcon } from "@plane/ui";
// helpers
import { copyTextToClipboard } from "helpers/string.helper";
// types
@ -80,12 +80,15 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
const [linkModal, setLinkModal] = useState(false);
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<linkDetails | null>(null);
const { user: userStore } = useMobxStore();
const {
user: userStore,
projectState: { states },
} = useMobxStore();
const user = userStore.currentUser;
const userRole = userStore.currentProjectRole;
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
const { workspaceSlug, projectId, issueId, inboxIssueId } = router.query;
const { isEstimateActive } = useEstimateOption();
@ -248,6 +251,10 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
const isAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER;
const currentIssueState = projectId
? states[projectId.toString()]?.find((s) => s.id === issueDetail?.state)
: undefined;
return (
<>
<LinkModal
@ -266,9 +273,20 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
)}
<div className="h-full w-full flex flex-col divide-y-2 divide-custom-border-200 overflow-hidden">
<div className="flex items-center justify-between px-5 pb-3">
<h4 className="text-sm font-medium">
{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id}
</h4>
<div className="flex items-center gap-x-2">
{currentIssueState ? (
<StateGroupIcon
className="h-4 w-4"
stateGroup={currentIssueState.group}
color={currentIssueState.color}
/>
) : inboxIssueId ? (
<StateGroupIcon className="h-4 w-4" stateGroup="backlog" color="#ff7700" />
) : null}
<h4 className="text-lg text-custom-text-300 font-medium">
{issueDetail?.project_detail?.identifier}-{issueDetail?.sequence_id}
</h4>
</div>
<div className="flex flex-wrap items-center gap-2">
{issueDetail?.created_by !== user?.id &&
!issueDetail?.assignees.includes(user?.id ?? "") &&