Merge pull request #64 from dakshesh14/main

fix: made description optional in project settings page, randomly selecting emoji
This commit is contained in:
Vihar Kurama 2022-12-21 19:21:39 +05:30 committed by GitHub
commit 847a0ab853
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 212 additions and 195 deletions

View File

@ -228,7 +228,7 @@ const SingleIssue: React.FC<Props> = ({
</div> </div>
</div> </div>
)} )}
{properties.target_date && ( {properties.due_date && (
<div <div
className={`group flex-shrink-0 group flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 ${ className={`group flex-shrink-0 group flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 ${
issue.target_date === null issue.target_date === null
@ -246,10 +246,10 @@ const SingleIssue: React.FC<Props> = ({
<div> <div>
{issue.target_date && {issue.target_date &&
(issue.target_date < new Date().toISOString() (issue.target_date < new Date().toISOString()
? `Target date has passed by ${findHowManyDaysLeft(issue.target_date)} days` ? `Due date has passed by ${findHowManyDaysLeft(issue.target_date)} days`
: findHowManyDaysLeft(issue.target_date) <= 3 : findHowManyDaysLeft(issue.target_date) <= 3
? `Target date is in ${findHowManyDaysLeft(issue.target_date)} days` ? `Due date is in ${findHowManyDaysLeft(issue.target_date)} days`
: "Target date")} : "Due date")}
</div> </div>
</div> </div>
</div> </div>

View File

@ -9,7 +9,7 @@ import { Dialog, Transition } from "@headlessui/react";
import projectServices from "lib/services/project.service"; import projectServices from "lib/services/project.service";
import workspaceService from "lib/services/workspace.service"; import workspaceService from "lib/services/workspace.service";
// common // common
import { createSimilarString } from "constants/common"; import { createSimilarString, getRandomEmoji } from "constants/common";
// constants // constants
import { NETWORK_CHOICES } from "constants/"; import { NETWORK_CHOICES } from "constants/";
// fetch keys // fetch keys
@ -32,7 +32,7 @@ const defaultValues: Partial<IProject> = {
identifier: "", identifier: "",
description: "", description: "",
network: 0, network: 0,
icon: "", icon: getRandomEmoji(),
}; };
const IsGuestCondition: React.FC<{ const IsGuestCondition: React.FC<{

View File

@ -218,7 +218,7 @@ const CyclesListView: React.FC<Props> = ({
</div> </div>
</div> </div>
)} )}
{properties.target_date && ( {properties.due_date && (
<div <div
className={`group relative flex-shrink-0 group flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 ${ className={`group relative flex-shrink-0 group flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 ${
issue.target_date === null issue.target_date === null
@ -234,23 +234,21 @@ const CyclesListView: React.FC<Props> = ({
? renderShortNumericDateFormat(issue.target_date) ? renderShortNumericDateFormat(issue.target_date)
: "N/A"} : "N/A"}
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap"> <div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
<h5 className="font-medium mb-1 text-gray-900"> <h5 className="font-medium mb-1 text-gray-900">Due date</h5>
Target date
</h5>
<div> <div>
{renderShortNumericDateFormat(issue.target_date ?? "")} {renderShortNumericDateFormat(issue.target_date ?? "")}
</div> </div>
<div> <div>
{issue.target_date && {issue.target_date &&
(issue.target_date < new Date().toISOString() (issue.target_date < new Date().toISOString()
? `Target date has passed by ${findHowManyDaysLeft( ? `Due date has passed by ${findHowManyDaysLeft(
issue.target_date issue.target_date
)} days` )} days`
: findHowManyDaysLeft(issue.target_date) <= 3 : findHowManyDaysLeft(issue.target_date) <= 3
? `Target date is in ${findHowManyDaysLeft( ? `Due date is in ${findHowManyDaysLeft(
issue.target_date issue.target_date
)} days` )} days`
: "Target date")} : "Due date")}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,16 @@
import React from "react";
// next // next
import { useRouter } from "next/router";
import Image from "next/image"; import Image from "next/image";
// swr
import useSWR from "swr";
// constants
import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
import { addSpaceIfCamelCase, timeAgo } from "constants/common";
// services
import issuesServices from "lib/services/issues.service";
// hooks
import useUser from "lib/hooks/useUser";
// ui // ui
import { Spinner } from "ui"; import { Spinner } from "ui";
// icons // icons
@ -12,14 +23,6 @@ import {
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
// types // types
import { IssueResponse, IState } from "types"; import { IssueResponse, IState } from "types";
// constants
import { addSpaceIfCamelCase, timeAgo } from "constants/common";
type Props = {
issueActivities: any[] | undefined;
states: IState[] | undefined;
issues: IssueResponse | undefined;
};
const activityIcons: { const activityIcons: {
[key: string]: JSX.Element; [key: string]: JSX.Element;
@ -32,7 +35,25 @@ const activityIcons: {
parent: <UserIcon className="h-3.5 w-3.5" />, parent: <UserIcon className="h-3.5 w-3.5" />,
}; };
const IssueActivitySection: React.FC<Props> = ({ issueActivities, states, issues }) => { const IssueActivitySection: React.FC = () => {
const router = useRouter();
const { issueId, projectId } = router.query;
const { activeWorkspace, states, issues } = useUser();
const { data: issueActivities } = useSWR<any[]>(
activeWorkspace && projectId && issueId ? PROJECT_ISSUES_ACTIVITY : null,
activeWorkspace && projectId && issueId
? () =>
issuesServices.getIssueActivities(
activeWorkspace.slug,
projectId as string,
issueId as string
)
: null
);
return ( return (
<> <>
{issueActivities ? ( {issueActivities ? (

View File

@ -1,12 +1,16 @@
import React from "react"; import React from "react";
// router
import { useRouter } from "next/router";
// swr // swr
import { mutate } from "swr"; import useSWR from "swr";
// react hook form // react hook form
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
// services // services
import issuesServices from "lib/services/issues.service"; import issuesServices from "lib/services/issues.service";
// fetch keys // fetch keys
import { PROJECT_ISSUES_COMMENTS } from "constants/fetch-keys"; import { PROJECT_ISSUES_COMMENTS } from "constants/fetch-keys";
// hooks
import useUser from "lib/hooks/useUser";
// components // components
import CommentCard from "components/project/issues/issue-detail/comment/IssueCommentCard"; import CommentCard from "components/project/issues/issue-detail/comment/IssueCommentCard";
// ui // ui
@ -14,18 +18,11 @@ import { TextArea, Button, Spinner } from "ui";
// types // types
import type { IIssueComment } from "types"; import type { IIssueComment } from "types";
type Props = {
comments?: IIssueComment[];
workspaceSlug: string;
projectId: string;
issueId: string;
};
const defaultValues: Partial<IIssueComment> = { const defaultValues: Partial<IIssueComment> = {
comment: "", comment: "",
}; };
const IssueCommentSection: React.FC<Props> = ({ comments, issueId, projectId, workspaceSlug }) => { const IssueCommentSection: React.FC = () => {
const { const {
register, register,
handleSubmit, handleSubmit,
@ -34,15 +31,31 @@ const IssueCommentSection: React.FC<Props> = ({ comments, issueId, projectId, wo
reset, reset,
} = useForm<IIssueComment>({ defaultValues }); } = useForm<IIssueComment>({ defaultValues });
const router = useRouter();
let { issueId, projectId } = router.query;
const { activeWorkspace } = useUser();
const { data: comments, mutate } = useSWR<IIssueComment[]>(
activeWorkspace && projectId && issueId ? PROJECT_ISSUES_COMMENTS(issueId as string) : null,
activeWorkspace && projectId && issueId
? () =>
issuesServices.getIssueComments(
activeWorkspace.slug,
projectId as string,
issueId as string
)
: null
);
const onSubmit = async (formData: IIssueComment) => { const onSubmit = async (formData: IIssueComment) => {
if (!activeWorkspace || !projectId || !issueId || isSubmitting) return;
await issuesServices await issuesServices
.createIssueComment(workspaceSlug, projectId, issueId, formData) .createIssueComment(activeWorkspace.slug, projectId as string, issueId as string, formData)
.then((response) => { .then((response) => {
console.log(response); console.log(response);
mutate<IIssueComment[]>(PROJECT_ISSUES_COMMENTS(issueId), (prevData) => [ mutate((prevData) => [response, ...(prevData ?? [])]);
response,
...(prevData ?? []),
]);
reset(defaultValues); reset(defaultValues);
}) })
.catch((error) => { .catch((error) => {
@ -51,26 +64,34 @@ const IssueCommentSection: React.FC<Props> = ({ comments, issueId, projectId, wo
}; };
const onCommentUpdate = async (comment: IIssueComment) => { const onCommentUpdate = async (comment: IIssueComment) => {
if (!activeWorkspace || !projectId || !issueId || isSubmitting) return;
await issuesServices await issuesServices
.patchIssueComment(workspaceSlug, projectId, issueId, comment.id, comment) .patchIssueComment(
activeWorkspace.slug,
projectId as string,
issueId as string,
comment.id,
comment
)
.then((response) => { .then((response) => {
console.log(response); mutate((prevData) => {
mutate<IIssueComment[]>(PROJECT_ISSUES_COMMENTS(issueId), (prevData) => { const updatedComments = prevData?.map((c) => {
const newData = prevData ?? []; if (c.id === comment.id) {
const index = newData.findIndex((comment) => comment.id === response.id); return comment;
newData[index] = response; }
return [...newData]; return c;
});
return updatedComments;
}); });
}); });
}; };
const onCommentDelete = async (commentId: string) => { const onCommentDelete = async (commentId: string) => {
if (!activeWorkspace || !projectId || !issueId || isSubmitting) return;
await issuesServices await issuesServices
.deleteIssueComment(workspaceSlug, projectId, issueId, commentId) .deleteIssueComment(activeWorkspace.slug, projectId as string, issueId as string, commentId)
.then((response) => { .then((response) => {
mutate<IIssueComment[]>(PROJECT_ISSUES_COMMENTS(issueId), (prevData) => mutate((prevData) => (prevData ?? []).filter((c) => c.id !== commentId));
(prevData ?? []).filter((c) => c.id !== commentId)
);
console.log(response); console.log(response);
}); });
}; };
@ -124,7 +145,6 @@ const IssueCommentSection: React.FC<Props> = ({ comments, issueId, projectId, wo
/> />
<Button type="submit" className="whitespace-nowrap" disabled={isSubmitting}> <Button type="submit" className="whitespace-nowrap" disabled={isSubmitting}>
{isSubmitting ? "Adding comment..." : "Add comment"} {isSubmitting ? "Adding comment..." : "Add comment"}
{/* <UploadingIcon /> */}
</Button> </Button>
</div> </div>
</form> </form>

View File

@ -1,6 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
// swr // swr
import useSWR from "swr"; import useSWR from "swr";
import dynamic from "next/dynamic";
// headless ui // headless ui
import { Listbox, Transition } from "@headlessui/react"; import { Listbox, Transition } from "@headlessui/react";
// react hook form // react hook form
@ -14,6 +15,8 @@ import useToast from "lib/hooks/useToast";
import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
// commons // commons
import { copyTextToClipboard } from "constants/common"; import { copyTextToClipboard } from "constants/common";
// components
import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion";
// ui // ui
import { Input, Button, Spinner } from "ui"; import { Input, Button, Spinner } from "ui";
import { Popover } from "@headlessui/react"; import { Popover } from "@headlessui/react";
@ -30,7 +33,7 @@ import {
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
// types // types
import type { Control } from "react-hook-form"; import type { Control } from "react-hook-form";
import type { IIssue, IIssueLabels, NestedKeyOf } from "types"; import type { ICycle, IIssue, IIssueLabels, NestedKeyOf } from "types";
import { TwitterPicker } from "react-color"; import { TwitterPicker } from "react-color";
import { positionEditorElement } from "components/lexical/helpers/editor"; import { positionEditorElement } from "components/lexical/helpers/editor";
import SelectState from "./select-state"; import SelectState from "./select-state";
@ -46,7 +49,6 @@ type Props = {
submitChanges: (formData: Partial<IIssue>) => void; submitChanges: (formData: Partial<IIssue>) => void;
issueDetail: IIssue | undefined; issueDetail: IIssue | undefined;
watch: UseFormWatch<IIssue>; watch: UseFormWatch<IIssue>;
setDeleteIssueModal: React.Dispatch<React.SetStateAction<boolean>>;
}; };
const defaultValues: Partial<IIssueLabels> = { const defaultValues: Partial<IIssueLabels> = {
@ -59,7 +61,6 @@ const IssueDetailSidebar: React.FC<Props> = ({
submitChanges, submitChanges,
issueDetail, issueDetail,
watch: watchIssue, watch: watchIssue,
setDeleteIssueModal,
}) => { }) => {
const [createLabelForm, setCreateLabelForm] = useState(false); const [createLabelForm, setCreateLabelForm] = useState(false);
@ -74,6 +75,8 @@ const IssueDetailSidebar: React.FC<Props> = ({
: null : null
); );
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const { const {
register, register,
handleSubmit, handleSubmit,
@ -97,15 +100,17 @@ const IssueDetailSidebar: React.FC<Props> = ({
}); });
}; };
const handleCycleChange = (cycleId: string) => { const handleCycleChange = (cycleDetail: ICycle) => {
if (activeWorkspace && activeProject && issueDetail) if (activeWorkspace && activeProject && issueDetail) {
submitChanges({ cycle: cycleDetail.id, cycle_detail: cycleDetail });
issuesServices issuesServices
.addIssueToCycle(activeWorkspace.slug, activeProject.id, cycleId, { .addIssueToCycle(activeWorkspace.slug, activeProject.id, cycleDetail.id, {
issues: [issueDetail.id], issues: [issueDetail.id],
}) })
.then(() => { .then(() => {
submitChanges({}); submitChanges({});
}); });
}
}; };
return ( return (
@ -420,6 +425,11 @@ const IssueDetailSidebar: React.FC<Props> = ({
)} )}
</div> </div>
</div> </div>
<ConfirmIssueDeletion
handleClose={() => setDeleteIssueModal(false)}
isOpen={deleteIssueModal}
data={issueDetail}
/>
</> </>
); );
}; };

View File

@ -9,13 +9,13 @@ import { Spinner, CustomSelect } from "ui";
// icons // icons
import { ArrowPathIcon } from "@heroicons/react/24/outline"; import { ArrowPathIcon } from "@heroicons/react/24/outline";
// types // types
import { IIssue } from "types"; import { ICycle, IIssue } from "types";
// common // common
import { classNames } from "constants/common"; import { classNames } from "constants/common";
type Props = { type Props = {
control: Control<IIssue, any>; control: Control<IIssue, any>;
handleCycleChange: (cycleId: string) => void; handleCycleChange: (cycle: ICycle) => void;
}; };
const SelectCycle: React.FC<Props> = ({ control, handleCycleChange }) => { const SelectCycle: React.FC<Props> = ({ control, handleCycleChange }) => {
@ -46,7 +46,7 @@ const SelectCycle: React.FC<Props> = ({ control, handleCycleChange }) => {
} }
value={value} value={value}
onChange={(value: any) => { onChange={(value: any) => {
handleCycleChange(value); handleCycleChange(cycles?.find((c) => c.id === value) as any);
}} }}
> >
{cycles ? ( {cycles ? (

View File

@ -310,7 +310,7 @@ const ListView: React.FC<Props> = ({
</div> </div>
</div> </div>
)} )}
{properties.target_date && ( {properties.due_date && (
<div <div
className={`group relative flex-shrink-0 group flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 ${ className={`group relative flex-shrink-0 group flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 ${
issue.target_date === null issue.target_date === null
@ -335,14 +335,14 @@ const ListView: React.FC<Props> = ({
<div> <div>
{issue.target_date && {issue.target_date &&
(issue.target_date < new Date().toISOString() (issue.target_date < new Date().toISOString()
? `Target date has passed by ${findHowManyDaysLeft( ? `Due date has passed by ${findHowManyDaysLeft(
issue.target_date issue.target_date
)} days` )} days`
: findHowManyDaysLeft(issue.target_date) <= 3 : findHowManyDaysLeft(issue.target_date) <= 3
? `Target date is in ${findHowManyDaysLeft( ? `Due date is in ${findHowManyDaysLeft(
issue.target_date issue.target_date
)} days` )} days`
: "Target date")} : "Due date")}
</div> </div>
</div> </div>
</div> </div>

View File

@ -16,7 +16,7 @@ const initialValues: Properties = {
assignee: true, assignee: true,
priority: false, priority: false,
start_date: false, start_date: false,
target_date: false, due_date: false,
cycle: false, cycle: false,
children_count: false, children_count: false,
}; };
@ -26,7 +26,7 @@ const useIssuesProperties = (workspaceSlug?: string, projectId?: string) => {
const { user } = useUser(); const { user } = useUser();
const { data: issueProperties } = useSWR<IssuePriorities>( const { data: issueProperties, mutate: mutateIssueProperties } = useSWR<IssuePriorities>(
workspaceSlug && projectId ? ISSUE_PROPERTIES_ENDPOINT(workspaceSlug, projectId) : null, workspaceSlug && projectId ? ISSUE_PROPERTIES_ENDPOINT(workspaceSlug, projectId) : null,
workspaceSlug && projectId workspaceSlug && projectId
? () => issueServices.getIssueProperties(workspaceSlug, projectId) ? () => issueServices.getIssueProperties(workspaceSlug, projectId)
@ -56,6 +56,14 @@ const useIssuesProperties = (workspaceSlug?: string, projectId?: string) => {
(key: keyof Properties) => { (key: keyof Properties) => {
if (!workspaceSlug || !projectId || !issueProperties || !user) return; if (!workspaceSlug || !projectId || !issueProperties || !user) return;
setProperties((prev) => ({ ...prev, [key]: !prev[key] })); setProperties((prev) => ({ ...prev, [key]: !prev[key] }));
mutateIssueProperties(
(prev) =>
({
...prev,
properties: { ...prev?.properties, [key]: !prev?.properties?.[key] },
} as IssuePriorities),
false
);
if (Object.keys(issueProperties).length > 0) { if (Object.keys(issueProperties).length > 0) {
issueServices.patchIssueProperties(workspaceSlug, projectId, issueProperties.id, { issueServices.patchIssueProperties(workspaceSlug, projectId, issueProperties.id, {
properties: { properties: {
@ -71,23 +79,19 @@ const useIssuesProperties = (workspaceSlug?: string, projectId?: string) => {
}); });
} }
}, },
[workspaceSlug, projectId, issueProperties, user] [workspaceSlug, projectId, issueProperties, user, mutateIssueProperties]
); );
const newProperties = Object.keys(properties).reduce((obj: any, key) => { const newProperties: Properties = {
if ( key: properties.key,
key !== "children" && state: properties.state,
key !== "name" && assignee: properties.assignee,
key !== "parent" && priority: properties.priority,
key !== "project" && start_date: properties.start_date,
key !== "description" && due_date: properties.due_date,
key !== "attachments" && cycle: properties.cycle,
key !== "sequence_id" children_count: properties.children_count,
) { };
obj[key] = properties[key as keyof Properties];
}
return obj;
}, {});
return [newProperties, updateIssueProperties] as const; return [newProperties, updateIssueProperties] as const;
}; };

View File

@ -16,7 +16,7 @@ const initialValues: Properties = {
assignee: true, assignee: true,
priority: false, priority: false,
start_date: false, start_date: false,
target_date: false, due_date: false,
cycle: false, cycle: false,
children_count: false, children_count: false,
}; };

View File

@ -398,7 +398,7 @@ const MyIssues: NextPage = () => {
</div> </div>
</div> </div>
)} )}
{properties.target_date && ( {properties.due_date && (
<div <div
className={`group relative flex-shrink-0 group flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 ${ className={`group relative flex-shrink-0 group flex items-center gap-1 hover:bg-gray-100 border rounded shadow-sm px-2 py-1 cursor-pointer focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 text-xs duration-300 ${
issue.target_date === null issue.target_date === null
@ -415,7 +415,7 @@ const MyIssues: NextPage = () => {
: "N/A"} : "N/A"}
<div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap"> <div className="absolute bottom-full right-0 mb-2 z-10 hidden group-hover:block p-2 bg-white shadow-md rounded-md whitespace-nowrap">
<h5 className="font-medium mb-1 text-gray-900"> <h5 className="font-medium mb-1 text-gray-900">
Target date Due date
</h5> </h5>
<div> <div>
{renderShortNumericDateFormat(issue.target_date ?? "")} {renderShortNumericDateFormat(issue.target_date ?? "")}
@ -423,14 +423,14 @@ const MyIssues: NextPage = () => {
<div> <div>
{issue.target_date && {issue.target_date &&
(issue.target_date < new Date().toISOString() (issue.target_date < new Date().toISOString()
? `Target date has passed by ${findHowManyDaysLeft( ? `Due date has passed by ${findHowManyDaysLeft(
issue.target_date issue.target_date
)} days` )} days`
: findHowManyDaysLeft(issue.target_date) <= 3 : findHowManyDaysLeft(issue.target_date) <= 3
? `Target date is in ${findHowManyDaysLeft( ? `Due date is in ${findHowManyDaysLeft(
issue.target_date issue.target_date
)} days` )} days`
: "Target date")} : "Due date")}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,21 +1,17 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
// next // next
import Link from "next/link"; import Link from "next/link";
import dynamic from "next/dynamic";
import type { NextPage } from "next"; import type { NextPage } from "next";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import dynamic from "next/dynamic";
// swr // swr
import useSWR, { mutate } from "swr"; import { mutate } from "swr";
// react hook form // react hook form
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
// headless ui // headless ui
import { Disclosure, Menu, Tab, Transition } from "@headlessui/react"; import { Disclosure, Menu, Tab, Transition } from "@headlessui/react";
// fetch keys // fetch keys
import { import { PROJECT_ISSUES_LIST } from "constants/fetch-keys";
PROJECT_ISSUES_ACTIVITY,
PROJECT_ISSUES_COMMENTS,
PROJECT_ISSUES_LIST,
} from "constants/fetch-keys";
// services // services
import issuesServices from "lib/services/issues.service"; import issuesServices from "lib/services/issues.service";
// common // common
@ -27,14 +23,23 @@ import withAuth from "lib/hoc/withAuthWrapper";
// layouts // layouts
import AppLayout from "layouts/app-layout"; import AppLayout from "layouts/app-layout";
// components // components
import CreateUpdateIssuesModal from "components/project/issues/create-update-issue-modal";
import IssueCommentSection from "components/project/issues/issue-detail/comment/IssueCommentSection";
import AddAsSubIssue from "components/project/issues/issue-detail/add-as-sub-issue"; import AddAsSubIssue from "components/project/issues/issue-detail/add-as-sub-issue";
import ConfirmIssueDeletion from "components/project/issues/confirm-issue-deletion"; import CreateUpdateIssuesModal from "components/project/issues/create-update-issue-modal";
import IssueDetailSidebar from "components/project/issues/issue-detail/issue-detail-sidebar"; import IssueDetailSidebar from "components/project/issues/issue-detail/issue-detail-sidebar";
import IssueActivitySection from "components/project/issues/issue-detail/activity"; import IssueCommentSection from "components/project/issues/issue-detail/comment/IssueCommentSection";
const IssueActivitySection = dynamic(
() => import("components/project/issues/issue-detail/activity"),
{
loading: () => (
<div className="w-full h-full flex justify-center items-center">
<Spinner />
</div>
),
ssr: false,
}
);
// ui // ui
import { Spinner, TextArea, HeaderButton, Breadcrumbs, BreadcrumbItem, CustomMenu } from "ui"; import { Spinner, TextArea, HeaderButton, Breadcrumbs } from "ui";
// icons // icons
import { import {
ChevronLeftIcon, ChevronLeftIcon,
@ -43,32 +48,51 @@ import {
PlusIcon, PlusIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
// types // types
import { IIssue, IIssueComment, IssueResponse } from "types"; import { IIssue, IssueResponse } from "types";
const RichTextEditor = dynamic(() => import("components/lexical/editor"), { const RichTextEditor = dynamic(() => import("components/lexical/editor"), {
ssr: false, ssr: false,
}); });
const defaultValues = {
name: "",
description: "",
state: "",
assignees_list: [],
priority: "low",
blockers_list: [],
blocked_list: [],
target_date: new Date().toString(),
issue_cycle: null,
labels_list: [],
};
const IssueDetail: NextPage = () => { const IssueDetail: NextPage = () => {
const [deleteIssueModal, setDeleteIssueModal] = useState(false); const router = useRouter();
const { issueId, projectId } = router.query;
const { activeWorkspace, activeProject, issues, mutateIssues } = useUser();
const issueDetail = issues?.results?.find((issue) => issue.id === issueId);
const prevIssue = issues?.results[issues?.results.findIndex((issue) => issue.id === issueId) - 1];
const nextIssue = issues?.results[issues?.results.findIndex((issue) => issue.id === issueId) + 1];
const subIssues = (issues && issues.results.filter((i) => i.parent === issueId)) ?? [];
const siblingIssues =
issueDetail &&
issues?.results.filter((i) => i.parent === issueDetail.parent && i.id !== issueId);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isAddAsSubIssueOpen, setIsAddAsSubIssueOpen] = useState(false); const [isAddAsSubIssueOpen, setIsAddAsSubIssueOpen] = useState(false);
const [issueDetail, setIssueDetail] = useState<IIssue | undefined>(undefined);
const [preloadedData, setPreloadedData] = useState< const [preloadedData, setPreloadedData] = useState<
(Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | undefined (Partial<IIssue> & { actionType: "createIssue" | "edit" | "delete" }) | undefined
>(undefined); >(undefined);
const [issueDescriptionValue, setIssueDescriptionValue] = useState(""); const [issueDescriptionValue, setIssueDescriptionValue] = useState("");
const router = useRouter();
const { issueId, projectId } = router.query;
const { activeWorkspace, activeProject, issues, mutateIssues, states } = useUser();
const handleDescriptionChange: any = (value: any) => { const handleDescriptionChange: any = (value: any) => {
console.log(value); console.log(value);
setIssueDescriptionValue(value); setIssueDescriptionValue(value);
@ -82,44 +106,9 @@ const IssueDetail: NextPage = () => {
control, control,
watch, watch,
} = useForm<IIssue>({ } = useForm<IIssue>({
defaultValues: { defaultValues,
name: "",
description: "",
state: "",
assignees_list: [],
priority: "low",
blockers_list: [],
blocked_list: [],
target_date: new Date().toString(),
issue_cycle: null,
labels_list: [],
},
}); });
const { data: issueActivities } = useSWR<any[]>(
activeWorkspace && projectId && issueId ? PROJECT_ISSUES_ACTIVITY : null,
activeWorkspace && projectId && issueId
? () =>
issuesServices.getIssueActivities(
activeWorkspace.slug,
projectId as string,
issueId as string
)
: null
);
const { data: issueComments } = useSWR<IIssueComment[]>(
activeWorkspace && projectId && issueId ? PROJECT_ISSUES_COMMENTS(issueId as string) : null,
activeWorkspace && projectId && issueId
? () =>
issuesServices.getIssueComments(
activeWorkspace.slug,
projectId as string,
issueId as string
)
: null
);
const submitChanges = useCallback( const submitChanges = useCallback(
(formData: Partial<IIssue>) => { (formData: Partial<IIssue>) => {
if (!activeWorkspace || !activeProject || !issueId) return; if (!activeWorkspace || !activeProject || !issueId) return;
@ -180,19 +169,6 @@ const IssueDetail: NextPage = () => {
}); });
}, [issueDetail, reset]); }, [issueDetail, reset]);
useEffect(() => {
const issueDetail = issues?.results.find((issue) => issue.id === issueId);
setIssueDetail(issueDetail);
}, [issueId, issues]);
const prevIssue = issues?.results[issues?.results.findIndex((issue) => issue.id === issueId) - 1];
const nextIssue = issues?.results[issues?.results.findIndex((issue) => issue.id === issueId) + 1];
const subIssues = (issues && issues.results.filter((i) => i.parent === issueId)) ?? [];
const siblingIssues =
issueDetail &&
issues?.results.filter((i) => i.parent === issueDetail.parent && i.id !== issueId);
const handleSubIssueRemove = (issueId: string) => { const handleSubIssueRemove = (issueId: string) => {
if (activeWorkspace && activeProject) { if (activeWorkspace && activeProject) {
issuesServices issuesServices
@ -215,19 +191,17 @@ const IssueDetail: NextPage = () => {
} }
}; };
console.log("Issue detail", issueDetail);
return ( return (
<AppLayout <AppLayout
noPadding={true} noPadding={true}
bg="secondary" bg="secondary"
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem <Breadcrumbs.BreadcrumbItem
title={`${activeProject?.name ?? "Project"} Issues`} title={`${activeProject?.name ?? "Project"} Issues`}
link={`/projects/${activeProject?.id}/issues`} link={`/projects/${activeProject?.id}/issues`}
/> />
<BreadcrumbItem <Breadcrumbs.BreadcrumbItem
title={`Issue ${activeProject?.identifier ?? "Project"}-${ title={`Issue ${activeProject?.identifier ?? "Project"}-${
issueDetail?.sequence_id ?? "..." issueDetail?.sequence_id ?? "..."
} Details`} } Details`}
@ -259,24 +233,23 @@ const IssueDetail: NextPage = () => {
</div> </div>
} }
> >
<CreateUpdateIssuesModal {isOpen && (
isOpen={isOpen} <CreateUpdateIssuesModal
setIsOpen={setIsOpen} isOpen={isOpen}
projectId={projectId as string} setIsOpen={setIsOpen}
prePopulateData={{ projectId={projectId as string}
...preloadedData, prePopulateData={{
}} ...preloadedData,
/> }}
<ConfirmIssueDeletion />
handleClose={() => setDeleteIssueModal(false)} )}
isOpen={deleteIssueModal} {isAddAsSubIssueOpen && (
data={issueDetail} <AddAsSubIssue
/> isOpen={isAddAsSubIssueOpen}
<AddAsSubIssue setIsOpen={setIsAddAsSubIssueOpen}
isOpen={isAddAsSubIssueOpen} parent={issueDetail}
setIsOpen={setIsAddAsSubIssueOpen} />
parent={issueDetail} )}
/>
{issueDetail && activeProject ? ( {issueDetail && activeProject ? (
<div className="h-full flex gap-5"> <div className="h-full flex gap-5">
<div className="basis-2/3 space-y-5 p-5"> <div className="basis-2/3 space-y-5 p-5">
@ -590,19 +563,10 @@ const IssueDetail: NextPage = () => {
</Tab.List> </Tab.List>
<Tab.Panels> <Tab.Panels>
<Tab.Panel> <Tab.Panel>
<IssueCommentSection <IssueCommentSection />
comments={issueComments}
workspaceSlug={activeWorkspace?.slug as string}
projectId={projectId as string}
issueId={issueId as string}
/>
</Tab.Panel> </Tab.Panel>
<Tab.Panel> <Tab.Panel>
<IssueActivitySection <IssueActivitySection />
issueActivities={issueActivities}
states={states}
issues={issues}
/>
</Tab.Panel> </Tab.Panel>
</Tab.Panels> </Tab.Panels>
</Tab.Group> </Tab.Group>
@ -615,7 +579,6 @@ const IssueDetail: NextPage = () => {
issueDetail={issueDetail} issueDetail={issueDetail}
submitChanges={submitChanges} submitChanges={submitChanges}
watch={watch} watch={watch}
setDeleteIssueModal={setDeleteIssueModal}
/> />
</div> </div>
</div> </div>

View File

@ -177,9 +177,7 @@ const GeneralSettings = () => {
error={errors.description} error={errors.description}
register={register} register={register}
placeholder="Enter project description" placeholder="Enter project description"
validations={{ validations={{}}
required: "Description is required",
}}
/> />
</div> </div>
<div> <div>

View File

@ -72,6 +72,7 @@ export interface IIssue {
blocked_issue_details: any[]; blocked_issue_details: any[];
sprints: string | null; sprints: string | null;
cycle: string | null; cycle: string | null;
cycle_detail: ICycle | null;
issue_cycle: IIssueCycle; issue_cycle: IIssueCycle;
} }
@ -130,7 +131,7 @@ export type Properties = {
assignee: boolean; assignee: boolean;
priority: boolean; priority: boolean;
start_date: boolean; start_date: boolean;
target_date: boolean; due_date: boolean;
cycle: boolean; cycle: boolean;
children_count: boolean; children_count: boolean;
}; };

View File

@ -6,7 +6,7 @@ type BreadcrumbsProps = {
children: any; children: any;
}; };
const Breadcrumbs: React.FC<BreadcrumbsProps> = ({ children }: BreadcrumbsProps) => { const Breadcrumbs = ({ children }: BreadcrumbsProps) => {
const router = useRouter(); const router = useRouter();
return ( return (
@ -54,4 +54,6 @@ const BreadcrumbItem: React.FC<BreadcrumbItemProps> = ({ title, link, icon }) =>
); );
}; };
Breadcrumbs.BreadcrumbItem = BreadcrumbItem;
export { Breadcrumbs, BreadcrumbItem }; export { Breadcrumbs, BreadcrumbItem };

View File

@ -39,7 +39,7 @@ const EmojiIconPicker: React.FC<Props> = ({ label, value, onChange }) => {
}); });
useEffect(() => { useEffect(() => {
if (!value) onChange(getRandomEmoji()); if (!value || value?.length === 0) onChange(getRandomEmoji());
}, [value, onChange]); }, [value, onChange]);
return ( return (