mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: store setup
This commit is contained in:
parent
50c330db65
commit
a328c530d0
@ -7,19 +7,14 @@ import analyticsService from "services/analytics.service";
|
|||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
import cyclesService from "services/cycles.service";
|
import cyclesService from "services/cycles.service";
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
import trackEventServices from "services/track-event.service";
|
import trackEventServices from "services/track_event.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useProjects from "hooks/use-projects";
|
import useProjects from "hooks/use-projects";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
import { PrimaryButton, SecondaryButton } from "components/ui";
|
import { PrimaryButton, SecondaryButton } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import {
|
import { ArrowDownTrayIcon, ArrowPathIcon, CalendarDaysIcon, UserGroupIcon } from "@heroicons/react/24/outline";
|
||||||
ArrowDownTrayIcon,
|
|
||||||
ArrowPathIcon,
|
|
||||||
CalendarDaysIcon,
|
|
||||||
UserGroupIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
import { ContrastIcon, LayerDiagonalIcon } from "components/icons";
|
import { ContrastIcon, LayerDiagonalIcon } from "components/icons";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderShortDate } from "helpers/date-time.helper";
|
import { renderShortDate } from "helpers/date-time.helper";
|
||||||
@ -46,13 +41,7 @@ type Props = {
|
|||||||
user: ICurrentUserResponse | undefined;
|
user: ICurrentUserResponse | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AnalyticsSidebar: React.FC<Props> = ({
|
export const AnalyticsSidebar: React.FC<Props> = ({ analytics, params, fullScreen, isProjectLevel = false, user }) => {
|
||||||
analytics,
|
|
||||||
params,
|
|
||||||
fullScreen,
|
|
||||||
isProjectLevel = false,
|
|
||||||
user,
|
|
||||||
}) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||||
|
|
||||||
@ -61,9 +50,7 @@ export const AnalyticsSidebar: React.FC<Props> = ({
|
|||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { data: projectDetails } = useSWR(
|
const { data: projectDetails } = useSWR(
|
||||||
workspaceSlug && projectId && !(cycleId || moduleId)
|
workspaceSlug && projectId && !(cycleId || moduleId) ? PROJECT_DETAILS(projectId.toString()) : null,
|
||||||
? PROJECT_DETAILS(projectId.toString())
|
|
||||||
: null,
|
|
||||||
workspaceSlug && projectId && !(cycleId || moduleId)
|
workspaceSlug && projectId && !(cycleId || moduleId)
|
||||||
? () => projectService.getProject(workspaceSlug.toString(), projectId.toString())
|
? () => projectService.getProject(workspaceSlug.toString(), projectId.toString())
|
||||||
: null
|
: null
|
||||||
@ -72,24 +59,14 @@ export const AnalyticsSidebar: React.FC<Props> = ({
|
|||||||
const { data: cycleDetails } = useSWR(
|
const { data: cycleDetails } = useSWR(
|
||||||
workspaceSlug && projectId && cycleId ? CYCLE_DETAILS(cycleId.toString()) : null,
|
workspaceSlug && projectId && cycleId ? CYCLE_DETAILS(cycleId.toString()) : null,
|
||||||
workspaceSlug && projectId && cycleId
|
workspaceSlug && projectId && cycleId
|
||||||
? () =>
|
? () => cyclesService.getCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId.toString())
|
||||||
cyclesService.getCycleDetails(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
cycleId.toString()
|
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: moduleDetails } = useSWR(
|
const { data: moduleDetails } = useSWR(
|
||||||
workspaceSlug && projectId && moduleId ? MODULE_DETAILS(moduleId.toString()) : null,
|
workspaceSlug && projectId && moduleId ? MODULE_DETAILS(moduleId.toString()) : null,
|
||||||
workspaceSlug && projectId && moduleId
|
workspaceSlug && projectId && moduleId
|
||||||
? () =>
|
? () => modulesService.getModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString())
|
||||||
modulesService.getModuleDetails(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
moduleId.toString()
|
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -178,8 +155,7 @@ export const AnalyticsSidebar: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedProjects =
|
const selectedProjects = params.project && params.project.length > 0 ? params.project : projects?.map((p) => p.id);
|
||||||
params.project && params.project.length > 0 ? params.project : projects?.map((p) => p.id);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -236,9 +212,7 @@ export const AnalyticsSidebar: React.FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
<h5 className="flex items-center gap-1">
|
<h5 className="flex items-center gap-1">
|
||||||
<p className="break-words">{truncateText(project.name, 20)}</p>
|
<p className="break-words">{truncateText(project.name, 20)}</p>
|
||||||
<span className="text-custom-text-200 text-xs ml-1">
|
<span className="text-custom-text-200 text-xs ml-1">({project.identifier})</span>
|
||||||
({project.identifier})
|
|
||||||
</span>
|
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 space-y-3 pl-2 w-full">
|
<div className="mt-4 space-y-3 pl-2 w-full">
|
||||||
@ -344,10 +318,7 @@ export const AnalyticsSidebar: React.FC<Props> = ({
|
|||||||
<div className="space-y-4 mt-4">
|
<div className="space-y-4 mt-4">
|
||||||
<div className="flex items-center gap-2 text-xs">
|
<div className="flex items-center gap-2 text-xs">
|
||||||
<h6 className="text-custom-text-200">Network</h6>
|
<h6 className="text-custom-text-200">Network</h6>
|
||||||
<span>
|
<span>{NETWORK_CHOICES.find((n) => n.key === projectDetails?.network)?.label ?? ""}</span>
|
||||||
{NETWORK_CHOICES.find((n) => n.key === projectDetails?.network)?.label ??
|
|
||||||
""}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,15 +13,11 @@ import analyticsService from "services/analytics.service";
|
|||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
import cyclesService from "services/cycles.service";
|
import cyclesService from "services/cycles.service";
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
import trackEventServices from "services/track-event.service";
|
import trackEventServices from "services/track_event.service";
|
||||||
// components
|
// components
|
||||||
import { CustomAnalytics, ScopeAndDemand } from "components/analytics";
|
import { CustomAnalytics, ScopeAndDemand } from "components/analytics";
|
||||||
// icons
|
// icons
|
||||||
import {
|
import { ArrowsPointingInIcon, ArrowsPointingOutIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
ArrowsPointingInIcon,
|
|
||||||
ArrowsPointingOutIcon,
|
|
||||||
XMarkIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
// types
|
// types
|
||||||
import { IAnalyticsParams, IWorkspace } from "types";
|
import { IAnalyticsParams, IWorkspace } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
@ -67,9 +63,7 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { data: projectDetails } = useSWR(
|
const { data: projectDetails } = useSWR(
|
||||||
workspaceSlug && projectId && !(cycleId || moduleId)
|
workspaceSlug && projectId && !(cycleId || moduleId) ? PROJECT_DETAILS(projectId.toString()) : null,
|
||||||
? PROJECT_DETAILS(projectId.toString())
|
|
||||||
: null,
|
|
||||||
workspaceSlug && projectId && !(cycleId || moduleId)
|
workspaceSlug && projectId && !(cycleId || moduleId)
|
||||||
? () => projectService.getProject(workspaceSlug.toString(), projectId.toString())
|
? () => projectService.getProject(workspaceSlug.toString(), projectId.toString())
|
||||||
: null
|
: null
|
||||||
@ -78,24 +72,14 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
|
|||||||
const { data: cycleDetails } = useSWR(
|
const { data: cycleDetails } = useSWR(
|
||||||
workspaceSlug && projectId && cycleId ? CYCLE_DETAILS(cycleId.toString()) : null,
|
workspaceSlug && projectId && cycleId ? CYCLE_DETAILS(cycleId.toString()) : null,
|
||||||
workspaceSlug && projectId && cycleId
|
workspaceSlug && projectId && cycleId
|
||||||
? () =>
|
? () => cyclesService.getCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId.toString())
|
||||||
cyclesService.getCycleDetails(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
cycleId.toString()
|
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: moduleDetails } = useSWR(
|
const { data: moduleDetails } = useSWR(
|
||||||
workspaceSlug && projectId && moduleId ? MODULE_DETAILS(moduleId.toString()) : null,
|
workspaceSlug && projectId && moduleId ? MODULE_DETAILS(moduleId.toString()) : null,
|
||||||
workspaceSlug && projectId && moduleId
|
workspaceSlug && projectId && moduleId
|
||||||
? () =>
|
? () => modulesService.getModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString())
|
||||||
modulesService.getModuleDetails(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
moduleId.toString()
|
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -134,8 +118,7 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
|
|||||||
eventPayload.moduleName = moduleDetails.name;
|
eventPayload.moduleName = moduleDetails.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventType =
|
const eventType = tab === "Scope and Demand" ? "SCOPE_AND_DEMAND_ANALYTICS" : "CUSTOM_ANALYTICS";
|
||||||
tab === "Scope and Demand" ? "SCOPE_AND_DEMAND_ANALYTICS" : "CUSTOM_ANALYTICS";
|
|
||||||
|
|
||||||
trackEventServices.trackAnalyticsEvent(
|
trackEventServices.trackAnalyticsEvent(
|
||||||
eventPayload,
|
eventPayload,
|
||||||
@ -150,9 +133,9 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`absolute top-0 z-30 h-full bg-custom-background-90 ${
|
className={`absolute top-0 z-30 h-full bg-custom-background-90 ${fullScreen ? "p-2 w-full" : "w-1/2"} ${
|
||||||
fullScreen ? "p-2 w-full" : "w-1/2"
|
isOpen ? "right-0" : "-right-full"
|
||||||
} ${isOpen ? "right-0" : "-right-full"} duration-300 transition-all`}
|
} duration-300 transition-all`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex h-full flex-col overflow-hidden border-custom-border-200 bg-custom-background-100 text-left ${
|
className={`flex h-full flex-col overflow-hidden border-custom-border-200 bg-custom-background-100 text-left ${
|
||||||
@ -161,8 +144,7 @@ export const AnalyticsProjectModal: React.FC<Props> = ({ isOpen, onClose }) => {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-4 bg-custom-background-100 px-5 py-4 text-sm">
|
<div className="flex items-center justify-between gap-4 bg-custom-background-100 px-5 py-4 text-sm">
|
||||||
<h3 className="break-words">
|
<h3 className="break-words">
|
||||||
Analytics for{" "}
|
Analytics for {cycleId ? cycleDetails?.name : moduleId ? moduleDetails?.name : projectDetails?.name}
|
||||||
{cycleId ? cycleDetails?.name : moduleId ? moduleDetails?.name : projectDetails?.name}
|
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
|
@ -12,7 +12,7 @@ import { Squares2X2Icon } from "@heroicons/react/24/outline";
|
|||||||
import { StateGroupIcon } from "components/icons";
|
import { StateGroupIcon } from "components/icons";
|
||||||
import { ArchiveX } from "lucide-react";
|
import { ArchiveX } from "lucide-react";
|
||||||
// services
|
// services
|
||||||
import stateService from "services/state.service";
|
import stateService from "services/project_state.service";
|
||||||
// constants
|
// constants
|
||||||
import { PROJECT_AUTOMATION_MONTHS } from "constants/project";
|
import { PROJECT_AUTOMATION_MONTHS } from "constants/project";
|
||||||
import { STATES_LIST } from "constants/fetch-keys";
|
import { STATES_LIST } from "constants/fetch-keys";
|
||||||
@ -34,9 +34,7 @@ export const AutoCloseAutomation: React.FC<Props> = ({ projectDetails, handleCha
|
|||||||
|
|
||||||
const { data: stateGroups } = useSWR(
|
const { data: stateGroups } = useSWR(
|
||||||
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? () => stateService.getStates(workspaceSlug as string, projectId as string) : null
|
||||||
? () => stateService.getStates(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
const states = getStatesList(stateGroups);
|
const states = getStatesList(stateGroups);
|
||||||
|
|
||||||
@ -57,9 +55,7 @@ export const AutoCloseAutomation: React.FC<Props> = ({ projectDetails, handleCha
|
|||||||
|
|
||||||
const defaultState = stateGroups && stateGroups.cancelled ? stateGroups.cancelled[0].id : null;
|
const defaultState = stateGroups && stateGroups.cancelled ? stateGroups.cancelled[0].id : null;
|
||||||
|
|
||||||
const selectedOption = states?.find(
|
const selectedOption = states?.find((s) => s.id === projectDetails?.default_state ?? defaultState);
|
||||||
(s) => s.id === projectDetails?.default_state ?? defaultState
|
|
||||||
);
|
|
||||||
const currentDefaultState = states?.find((s) => s.id === defaultState);
|
const currentDefaultState = states?.find((s) => s.id === defaultState);
|
||||||
|
|
||||||
const initialValues: Partial<IProject> = {
|
const initialValues: Partial<IProject> = {
|
||||||
@ -105,15 +101,11 @@ export const AutoCloseAutomation: React.FC<Props> = ({ projectDetails, handleCha
|
|||||||
<div className="ml-12">
|
<div className="ml-12">
|
||||||
<div className="flex flex-col rounded bg-custom-background-90 border border-custom-border-200 p-2">
|
<div className="flex flex-col rounded bg-custom-background-90 border border-custom-border-200 p-2">
|
||||||
<div className="flex items-center justify-between px-5 py-4 gap-2 w-full">
|
<div className="flex items-center justify-between px-5 py-4 gap-2 w-full">
|
||||||
<div className="w-1/2 text-sm font-medium">
|
<div className="w-1/2 text-sm font-medium">Auto-close issues that are inactive for</div>
|
||||||
Auto-close issues that are inactive for
|
|
||||||
</div>
|
|
||||||
<div className="w-1/2">
|
<div className="w-1/2">
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
value={projectDetails?.close_in}
|
value={projectDetails?.close_in}
|
||||||
label={`${projectDetails?.close_in} ${
|
label={`${projectDetails?.close_in} ${projectDetails?.close_in === 1 ? "Month" : "Months"}`}
|
||||||
projectDetails?.close_in === 1 ? "Month" : "Months"
|
|
||||||
}`}
|
|
||||||
onChange={(val: number) => {
|
onChange={(val: number) => {
|
||||||
handleChange({ close_in: val });
|
handleChange({ close_in: val });
|
||||||
}}
|
}}
|
||||||
@ -142,9 +134,7 @@ export const AutoCloseAutomation: React.FC<Props> = ({ projectDetails, handleCha
|
|||||||
<div className="w-1/2 text-sm font-medium">Auto-close Status</div>
|
<div className="w-1/2 text-sm font-medium">Auto-close Status</div>
|
||||||
<div className="w-1/2 ">
|
<div className="w-1/2 ">
|
||||||
<CustomSearchSelect
|
<CustomSearchSelect
|
||||||
value={
|
value={projectDetails?.default_state ? projectDetails?.default_state : defaultState}
|
||||||
projectDetails?.default_state ? projectDetails?.default_state : defaultState
|
|
||||||
}
|
|
||||||
label={
|
label={
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{selectedOption ? (
|
{selectedOption ? (
|
||||||
@ -166,9 +156,7 @@ export const AutoCloseAutomation: React.FC<Props> = ({ projectDetails, handleCha
|
|||||||
)}
|
)}
|
||||||
{selectedOption?.name
|
{selectedOption?.name
|
||||||
? selectedOption.name
|
? selectedOption.name
|
||||||
: currentDefaultState?.name ?? (
|
: currentDefaultState?.name ?? <span className="text-custom-text-200">State</span>}
|
||||||
<span className="text-custom-text-200">State</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
onChange={(val: string) => {
|
onChange={(val: string) => {
|
||||||
|
@ -10,7 +10,7 @@ import { Command } from "cmdk";
|
|||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import workspaceService from "services/workspace.service";
|
import workspaceService from "services/workspace.service";
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
import inboxService from "services/inbox.service";
|
import inboxService from "services/inbox.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
import useProjectDetails from "hooks/use-project-details";
|
||||||
@ -79,16 +79,13 @@ export const CommandK: React.FC<Props> = ({ deleteIssue, isPaletteOpen, setIsPal
|
|||||||
const { data: issueDetails } = useSWR(
|
const { data: issueDetails } = useSWR(
|
||||||
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null,
|
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null,
|
||||||
workspaceSlug && projectId && issueId
|
workspaceSlug && projectId && issueId
|
||||||
? () =>
|
? () => issuesService.retrieve(workspaceSlug as string, projectId as string, issueId as string)
|
||||||
issuesService.retrieve(workspaceSlug as string, projectId as string, issueId as string)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: inboxList } = useSWR(
|
const { data: inboxList } = useSWR(
|
||||||
workspaceSlug && projectId ? INBOX_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? INBOX_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? () => inboxService.getInboxes(workspaceSlug as string, projectId as string) : null
|
||||||
? () => inboxService.getInboxes(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateIssue = useCallback(
|
const updateIssue = useCallback(
|
||||||
@ -272,8 +269,7 @@ export const CommandK: React.FC<Props> = ({ deleteIssue, isPaletteOpen, setIsPal
|
|||||||
>
|
>
|
||||||
{issueDetails && (
|
{issueDetails && (
|
||||||
<div className="overflow-hidden truncate rounded-md bg-custom-background-80 p-2 text-xs font-medium text-custom-text-200">
|
<div className="overflow-hidden truncate rounded-md bg-custom-background-80 p-2 text-xs font-medium text-custom-text-200">
|
||||||
{issueDetails.project_detail.identifier}-{issueDetails.sequence_id}{" "}
|
{issueDetails.project_detail.identifier}-{issueDetails.sequence_id} {issueDetails.name}
|
||||||
{issueDetails.name}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{projectId && (
|
{projectId && (
|
||||||
@ -324,12 +320,9 @@ export const CommandK: React.FC<Props> = ({ deleteIssue, isPaletteOpen, setIsPal
|
|||||||
</h5>
|
</h5>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isLoading &&
|
{!isLoading && resultsCount === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && (
|
||||||
resultsCount === 0 &&
|
<div className="my-4 text-center text-custom-text-200">No results found.</div>
|
||||||
searchTerm !== "" &&
|
)}
|
||||||
debouncedSearchTerm !== "" && (
|
|
||||||
<div className="my-4 text-center text-custom-text-200">No results found.</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{(isLoading || isSearching) && (
|
{(isLoading || isSearching) && (
|
||||||
<Command.Loading>
|
<Command.Loading>
|
||||||
@ -362,9 +355,7 @@ export const CommandK: React.FC<Props> = ({ deleteIssue, isPaletteOpen, setIsPal
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 overflow-hidden text-custom-text-200">
|
<div className="flex items-center gap-2 overflow-hidden text-custom-text-200">
|
||||||
<Icon iconName={currentSection.icon} />
|
<Icon iconName={currentSection.icon} />
|
||||||
<p className="block flex-1 truncate">
|
<p className="block flex-1 truncate">{currentSection.itemName(item)}</p>
|
||||||
{currentSection.itemName(item)}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</Command.Item>
|
</Command.Item>
|
||||||
))}
|
))}
|
||||||
@ -577,9 +568,7 @@ export const CommandK: React.FC<Props> = ({ deleteIssue, isPaletteOpen, setIsPal
|
|||||||
<Command.Item
|
<Command.Item
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
setIsPaletteOpen(false);
|
setIsPaletteOpen(false);
|
||||||
redirect(
|
redirect(`/${workspaceSlug}/projects/${projectId}/inbox/${inboxList?.[0]?.id}`);
|
||||||
`/${workspaceSlug}/projects/${projectId}/inbox/${inboxList?.[0]?.id}`
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
className="focus:outline-none"
|
className="focus:outline-none"
|
||||||
>
|
>
|
||||||
@ -672,10 +661,7 @@ export const CommandK: React.FC<Props> = ({ deleteIssue, isPaletteOpen, setIsPal
|
|||||||
<Command.Item
|
<Command.Item
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
setIsPaletteOpen(false);
|
setIsPaletteOpen(false);
|
||||||
window.open(
|
window.open("https://github.com/makeplane/plane/issues/new/choose", "_blank");
|
||||||
"https://github.com/makeplane/plane/issues/new/choose",
|
|
||||||
"_blank"
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
className="focus:outline-none"
|
className="focus:outline-none"
|
||||||
>
|
>
|
||||||
@ -759,29 +745,15 @@ export const CommandK: React.FC<Props> = ({ deleteIssue, isPaletteOpen, setIsPal
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{page === "change-issue-state" && issueDetails && (
|
{page === "change-issue-state" && issueDetails && (
|
||||||
<ChangeIssueState
|
<ChangeIssueState issue={issueDetails} setIsPaletteOpen={setIsPaletteOpen} user={user} />
|
||||||
issue={issueDetails}
|
|
||||||
setIsPaletteOpen={setIsPaletteOpen}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{page === "change-issue-priority" && issueDetails && (
|
{page === "change-issue-priority" && issueDetails && (
|
||||||
<ChangeIssuePriority
|
<ChangeIssuePriority issue={issueDetails} setIsPaletteOpen={setIsPaletteOpen} user={user} />
|
||||||
issue={issueDetails}
|
|
||||||
setIsPaletteOpen={setIsPaletteOpen}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{page === "change-issue-assignee" && issueDetails && (
|
{page === "change-issue-assignee" && issueDetails && (
|
||||||
<ChangeIssueAssignee
|
<ChangeIssueAssignee issue={issueDetails} setIsPaletteOpen={setIsPaletteOpen} user={user} />
|
||||||
issue={issueDetails}
|
|
||||||
setIsPaletteOpen={setIsPaletteOpen}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{page === "change-interface-theme" && (
|
|
||||||
<ChangeInterfaceTheme setIsPaletteOpen={setIsPaletteOpen} />
|
|
||||||
)}
|
)}
|
||||||
|
{page === "change-interface-theme" && <ChangeInterfaceTheme setIsPaletteOpen={setIsPaletteOpen} />}
|
||||||
</Command.List>
|
</Command.List>
|
||||||
</Command>
|
</Command>
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
|
@ -17,7 +17,7 @@ import { CreateUpdatePageModal } from "components/pages";
|
|||||||
// helpers
|
// helpers
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard } from "helpers/string.helper";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
import inboxService from "services/inbox.service";
|
import inboxService from "services/inbox.service";
|
||||||
// fetch keys
|
// fetch keys
|
||||||
import { INBOX_LIST, ISSUE_DETAILS } from "constants/fetch-keys";
|
import { INBOX_LIST, ISSUE_DETAILS } from "constants/fetch-keys";
|
||||||
@ -50,8 +50,7 @@ export const CommandPalette: React.FC = observer(() => {
|
|||||||
const { data: issueDetails } = useSWR(
|
const { data: issueDetails } = useSWR(
|
||||||
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null,
|
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null,
|
||||||
workspaceSlug && projectId && issueId
|
workspaceSlug && projectId && issueId
|
||||||
? () =>
|
? () => issuesService.retrieve(workspaceSlug as string, projectId as string, issueId as string)
|
||||||
issuesService.retrieve(workspaceSlug as string, projectId as string, issueId as string)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -141,11 +140,7 @@ export const CommandPalette: React.FC = observer(() => {
|
|||||||
<>
|
<>
|
||||||
<ShortcutsModal isOpen={isShortcutsModalOpen} setIsOpen={setIsShortcutsModalOpen} />
|
<ShortcutsModal isOpen={isShortcutsModalOpen} setIsOpen={setIsShortcutsModalOpen} />
|
||||||
{workspaceSlug && (
|
{workspaceSlug && (
|
||||||
<CreateProjectModal
|
<CreateProjectModal isOpen={isProjectModalOpen} setIsOpen={setIsProjectModalOpen} user={user} />
|
||||||
isOpen={isProjectModalOpen}
|
|
||||||
setIsOpen={setIsProjectModalOpen}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{projectId && (
|
{projectId && (
|
||||||
<>
|
<>
|
||||||
@ -184,11 +179,7 @@ export const CommandPalette: React.FC = observer(() => {
|
|||||||
handleClose={() => setIsIssueModalOpen(false)}
|
handleClose={() => setIsIssueModalOpen(false)}
|
||||||
fieldsToShow={inboxId ? ["name", "description", "priority"] : ["all"]}
|
fieldsToShow={inboxId ? ["name", "description", "priority"] : ["all"]}
|
||||||
prePopulateData={
|
prePopulateData={
|
||||||
cycleId
|
cycleId ? { cycle: cycleId.toString() } : moduleId ? { module: moduleId.toString() } : undefined
|
||||||
? { cycle: cycleId.toString() }
|
|
||||||
: moduleId
|
|
||||||
? { module: moduleId.toString() }
|
|
||||||
: undefined
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<BulkDeleteIssuesModal
|
<BulkDeleteIssuesModal
|
||||||
@ -196,11 +187,7 @@ export const CommandPalette: React.FC = observer(() => {
|
|||||||
setIsOpen={setIsBulkDeleteIssuesModalOpen}
|
setIsOpen={setIsBulkDeleteIssuesModalOpen}
|
||||||
user={user}
|
user={user}
|
||||||
/>
|
/>
|
||||||
<CommandK
|
<CommandK deleteIssue={deleteIssue} isPaletteOpen={isPaletteOpen} setIsPaletteOpen={setIsPaletteOpen} />
|
||||||
deleteIssue={deleteIssue}
|
|
||||||
isPaletteOpen={isPaletteOpen}
|
|
||||||
setIsPaletteOpen={setIsPaletteOpen}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -7,7 +7,7 @@ import { mutate } from "swr";
|
|||||||
// cmdk
|
// cmdk
|
||||||
import { Command } from "cmdk";
|
import { Command } from "cmdk";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useProjectMembers from "hooks/use-project-members";
|
import useProjectMembers from "hooks/use-project-members";
|
||||||
// constants
|
// constants
|
||||||
|
@ -7,7 +7,7 @@ import { mutate } from "swr";
|
|||||||
// cmdk
|
// cmdk
|
||||||
import { Command } from "cmdk";
|
import { Command } from "cmdk";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IIssue, TIssuePriorities } from "types";
|
import { ICurrentUserResponse, IIssue, TIssuePriorities } from "types";
|
||||||
// constants
|
// constants
|
||||||
@ -64,11 +64,7 @@ export const ChangeIssuePriority: React.FC<Props> = ({ setIsPaletteOpen, issue,
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{PRIORITIES.map((priority) => (
|
{PRIORITIES.map((priority) => (
|
||||||
<Command.Item
|
<Command.Item key={priority} onSelect={() => handleIssueState(priority)} className="focus:outline-none">
|
||||||
key={priority}
|
|
||||||
onSelect={() => handleIssueState(priority)}
|
|
||||||
className="focus:outline-none"
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<PriorityIcon priority={priority} />
|
<PriorityIcon priority={priority} />
|
||||||
<span className="capitalize">{priority ?? "None"}</span>
|
<span className="capitalize">{priority ?? "None"}</span>
|
||||||
|
@ -7,8 +7,8 @@ import useSWR, { mutate } from "swr";
|
|||||||
// cmdk
|
// cmdk
|
||||||
import { Command } from "cmdk";
|
import { Command } from "cmdk";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
import stateService from "services/state.service";
|
import stateService from "services/project_state.service";
|
||||||
// ui
|
// ui
|
||||||
import { Spinner } from "components/ui";
|
import { Spinner } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
@ -32,9 +32,7 @@ export const ChangeIssueState: React.FC<Props> = ({ setIsPaletteOpen, issue, use
|
|||||||
|
|
||||||
const { data: stateGroups, mutate: mutateIssueDetails } = useSWR(
|
const { data: stateGroups, mutate: mutateIssueDetails } = useSWR(
|
||||||
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? () => stateService.getStates(workspaceSlug as string, projectId as string) : null
|
||||||
? () => stateService.getStates(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
const states = getStatesList(stateGroups);
|
const states = getStatesList(stateGroups);
|
||||||
|
|
||||||
@ -78,18 +76,9 @@ export const ChangeIssueState: React.FC<Props> = ({ setIsPaletteOpen, issue, use
|
|||||||
{states ? (
|
{states ? (
|
||||||
states.length > 0 ? (
|
states.length > 0 ? (
|
||||||
states.map((state) => (
|
states.map((state) => (
|
||||||
<Command.Item
|
<Command.Item key={state.id} onSelect={() => handleIssueState(state.id)} className="focus:outline-none">
|
||||||
key={state.id}
|
|
||||||
onSelect={() => handleIssueState(state.id)}
|
|
||||||
className="focus:outline-none"
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<StateGroupIcon
|
<StateGroupIcon stateGroup={state.group} color={state.color} height="16px" width="16px" />
|
||||||
stateGroup={state.group}
|
|
||||||
color={state.color}
|
|
||||||
height="16px"
|
|
||||||
width="16px"
|
|
||||||
/>
|
|
||||||
<p>{state.name}</p>
|
<p>{state.name}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>{state.id === issue.state && <CheckIcon className="h-3 w-3" />}</div>
|
<div>{state.id === issue.state && <CheckIcon className="h-3 w-3" />}</div>
|
||||||
|
@ -9,7 +9,7 @@ import { SubmitHandler, useForm } from "react-hook-form";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Combobox, Dialog, Transition } from "@headlessui/react";
|
import { Combobox, Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesServices from "services/issues.service";
|
import issuesServices from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
@ -49,12 +49,8 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen, user
|
|||||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
||||||
|
|
||||||
const { data: issues } = useSWR(
|
const { data: issues } = useSWR(
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) : null,
|
||||||
? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string)
|
workspaceSlug && projectId ? () => issuesServices.getIssues(workspaceSlug as string, projectId as string) : null
|
||||||
: null,
|
|
||||||
workspaceSlug && projectId
|
|
||||||
? () => issuesServices.getIssues(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
@ -155,9 +151,7 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen, user
|
|||||||
: issues?.filter(
|
: issues?.filter(
|
||||||
(issue) =>
|
(issue) =>
|
||||||
issue.name.toLowerCase().includes(query.toLowerCase()) ||
|
issue.name.toLowerCase().includes(query.toLowerCase()) ||
|
||||||
`${issue.project_detail.identifier}-${issue.sequence_id}`
|
`${issue.project_detail.identifier}-${issue.sequence_id}`.toLowerCase().includes(query.toLowerCase())
|
||||||
.toLowerCase()
|
|
||||||
.includes(query.toLowerCase())
|
|
||||||
) ?? [];
|
) ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -4,7 +4,7 @@ import { useRouter } from "next/router";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
// services
|
// services
|
||||||
import aiService from "services/ai.service";
|
import aiService from "services/ai.service";
|
||||||
import trackEventServices from "services/track-event.service";
|
import trackEventServices from "services/track_event.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useUserAuth from "hooks/use-user-auth";
|
import useUserAuth from "hooks/use-user-auth";
|
||||||
@ -110,9 +110,7 @@ export const GptAssistantModal: React.FC<Props> = ({
|
|||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message:
|
message: error || "You have reached the maximum number of requests of 50 requests per month per user.",
|
||||||
error ||
|
|
||||||
"You have reached the maximum number of requests of 50 requests per month per user.",
|
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
@ -166,8 +164,7 @@ export const GptAssistantModal: React.FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
{invalidResponse && (
|
{invalidResponse && (
|
||||||
<div className="text-sm text-red-500">
|
<div className="text-sm text-red-500">
|
||||||
No response could be generated. This may be due to insufficient content or task
|
No response could be generated. This may be due to insufficient content or task information. Please try again.
|
||||||
information. Please try again.
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Input
|
<Input
|
||||||
@ -175,9 +172,7 @@ export const GptAssistantModal: React.FC<Props> = ({
|
|||||||
name="task"
|
name="task"
|
||||||
register={register}
|
register={register}
|
||||||
placeholder={`${
|
placeholder={`${
|
||||||
content && content !== ""
|
content && content !== "" ? "Tell AI what action to perform on this content..." : "Ask AI anything..."
|
||||||
? "Tell AI what action to perform on this content..."
|
|
||||||
: "Ask AI anything..."
|
|
||||||
}`}
|
}`}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
@ -187,18 +182,8 @@ export const GptAssistantModal: React.FC<Props> = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
onResponse(response);
|
onResponse(response);
|
||||||
onClose();
|
onClose();
|
||||||
if (block)
|
if (block) trackEventServices.trackUseGPTResponseEvent(block, "USE_GPT_RESPONSE_IN_PAGE_BLOCK", user);
|
||||||
trackEventServices.trackUseGPTResponseEvent(
|
else if (issue) trackEventServices.trackUseGPTResponseEvent(issue, "USE_GPT_RESPONSE_IN_ISSUE", user);
|
||||||
block,
|
|
||||||
"USE_GPT_RESPONSE_IN_PAGE_BLOCK",
|
|
||||||
user
|
|
||||||
);
|
|
||||||
else if (issue)
|
|
||||||
trackEventServices.trackUseGPTResponseEvent(
|
|
||||||
issue,
|
|
||||||
"USE_GPT_RESPONSE_IN_ISSUE",
|
|
||||||
user
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Use this response
|
Use this response
|
||||||
@ -206,16 +191,8 @@ export const GptAssistantModal: React.FC<Props> = ({
|
|||||||
)}
|
)}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<SecondaryButton onClick={onClose}>Close</SecondaryButton>
|
<SecondaryButton onClick={onClose}>Close</SecondaryButton>
|
||||||
<PrimaryButton
|
<PrimaryButton type="button" onClick={handleSubmit(handleResponse)} loading={isSubmitting}>
|
||||||
type="button"
|
{isSubmitting ? "Generating response..." : response === "" ? "Generate response" : "Generate again"}
|
||||||
onClick={handleSubmit(handleResponse)}
|
|
||||||
loading={isSubmitting}
|
|
||||||
>
|
|
||||||
{isSubmitting
|
|
||||||
? "Generating response..."
|
|
||||||
: response === ""
|
|
||||||
? "Generate response"
|
|
||||||
: "Generate again"}
|
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,18 +8,12 @@ import useSWR from "swr";
|
|||||||
import { DragDropContext, DropResult } from "react-beautiful-dnd";
|
import { DragDropContext, DropResult } from "react-beautiful-dnd";
|
||||||
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
|
||||||
// services
|
// services
|
||||||
import stateService from "services/state.service";
|
import stateService from "services/project_state.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
import { useProjectMyMembership } from "contexts/project-member.context";
|
import { useProjectMyMembership } from "contexts/project-member.context";
|
||||||
// components
|
// components
|
||||||
import {
|
import { AllLists, AllBoards, CalendarView, SpreadsheetView, GanttChartView } from "components/core";
|
||||||
AllLists,
|
|
||||||
AllBoards,
|
|
||||||
CalendarView,
|
|
||||||
SpreadsheetView,
|
|
||||||
GanttChartView,
|
|
||||||
} from "components/core";
|
|
||||||
// ui
|
// ui
|
||||||
import { EmptyState, Spinner } from "components/ui";
|
import { EmptyState, Spinner } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
@ -88,9 +82,7 @@ export const AllViews: React.FC<Props> = ({
|
|||||||
|
|
||||||
const { data: stateGroups } = useSWR(
|
const { data: stateGroups } = useSWR(
|
||||||
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
||||||
workspaceSlug
|
workspaceSlug ? () => stateService.getStates(workspaceSlug as string, projectId as string) : null
|
||||||
? () => stateService.getStates(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
const states = getStatesList(stateGroups);
|
const states = getStatesList(stateGroups);
|
||||||
|
|
||||||
@ -180,9 +172,7 @@ export const AllViews: React.FC<Props> = ({
|
|||||||
userAuth={memberRole}
|
userAuth={memberRole}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
displayFilters?.layout === "gantt_chart" && (
|
displayFilters?.layout === "gantt_chart" && <GanttChartView disableUserActions={disableUserActions} />
|
||||||
<GanttChartView disableUserActions={disableUserActions} />
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : router.pathname.includes("archived-issues") ? (
|
) : router.pathname.includes("archived-issues") ? (
|
||||||
|
@ -5,7 +5,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useProjects from "hooks/use-projects";
|
import useProjects from "hooks/use-projects";
|
||||||
@ -106,12 +106,7 @@ export const BoardHeader: React.FC<Props> = ({
|
|||||||
switch (displayFilters?.group_by) {
|
switch (displayFilters?.group_by) {
|
||||||
case "state":
|
case "state":
|
||||||
icon = currentState && (
|
icon = currentState && (
|
||||||
<StateGroupIcon
|
<StateGroupIcon stateGroup={currentState.group} color={currentState.color} height="16px" width="16px" />
|
||||||
stateGroup={currentState.group}
|
|
||||||
color={currentState.color}
|
|
||||||
height="16px"
|
|
||||||
width="16px"
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "state_detail.group":
|
case "state_detail.group":
|
||||||
@ -138,14 +133,8 @@ export const BoardHeader: React.FC<Props> = ({
|
|||||||
: null);
|
: null);
|
||||||
break;
|
break;
|
||||||
case "labels":
|
case "labels":
|
||||||
const labelColor =
|
const labelColor = issueLabels?.find((label) => label.id === groupTitle)?.color ?? "#000000";
|
||||||
issueLabels?.find((label) => label.id === groupTitle)?.color ?? "#000000";
|
icon = <span className="h-3.5 w-3.5 flex-shrink-0 rounded-full" style={{ backgroundColor: labelColor }} />;
|
||||||
icon = (
|
|
||||||
<span
|
|
||||||
className="h-3.5 w-3.5 flex-shrink-0 rounded-full"
|
|
||||||
style={{ backgroundColor: labelColor }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case "assignees":
|
case "assignees":
|
||||||
case "created_by":
|
case "created_by":
|
||||||
@ -196,10 +185,7 @@ export const BoardHeader: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isCollapsed ? (
|
{isCollapsed ? (
|
||||||
<Icon
|
<Icon iconName="close_fullscreen" className="text-base font-medium text-custom-text-900" />
|
||||||
iconName="close_fullscreen"
|
|
||||||
className="text-base font-medium text-custom-text-900"
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<Icon iconName="open_in_full" className="text-base font-medium text-custom-text-900" />
|
<Icon iconName="open_in_full" className="text-base font-medium text-custom-text-900" />
|
||||||
)}
|
)}
|
||||||
|
@ -5,14 +5,9 @@ import { useRouter } from "next/router";
|
|||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
|
|
||||||
// react-beautiful-dnd
|
// react-beautiful-dnd
|
||||||
import {
|
import { DraggableProvided, DraggableStateSnapshot, DraggingStyle, NotDraggingStyle } from "react-beautiful-dnd";
|
||||||
DraggableProvided,
|
|
||||||
DraggableStateSnapshot,
|
|
||||||
DraggingStyle,
|
|
||||||
NotDraggingStyle,
|
|
||||||
} from "react-beautiful-dnd";
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||||
@ -147,22 +142,17 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
issuesService
|
issuesService.patchIssue(workspaceSlug as string, issue.project, issue.id, formData, user).then(() => {
|
||||||
.patchIssue(workspaceSlug as string, issue.project, issue.id, formData, user)
|
mutateIssues();
|
||||||
.then(() => {
|
|
||||||
mutateIssues();
|
|
||||||
|
|
||||||
if (cycleId) mutate(CYCLE_DETAILS(cycleId as string));
|
if (cycleId) mutate(CYCLE_DETAILS(cycleId as string));
|
||||||
if (moduleId) mutate(MODULE_DETAILS(moduleId as string));
|
if (moduleId) mutate(MODULE_DETAILS(moduleId as string));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[displayFilters, workspaceSlug, cycleId, moduleId, groupTitle, index, mutateIssues, user]
|
[displayFilters, workspaceSlug, cycleId, moduleId, groupTitle, index, mutateIssues, user]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getStyle = (
|
const getStyle = (style: DraggingStyle | NotDraggingStyle | undefined, snapshot: DraggableStateSnapshot) => {
|
||||||
style: DraggingStyle | NotDraggingStyle | undefined,
|
|
||||||
snapshot: DraggableStateSnapshot
|
|
||||||
) => {
|
|
||||||
if (displayFilters?.order_by === "sort_order") return style;
|
if (displayFilters?.order_by === "sort_order") return style;
|
||||||
if (!snapshot.isDragging) return {};
|
if (!snapshot.isDragging) return {};
|
||||||
if (!snapshot.isDropAnimating) return style;
|
if (!snapshot.isDropAnimating) return style;
|
||||||
@ -174,11 +164,8 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyText = () => {
|
const handleCopyText = () => {
|
||||||
const originURL =
|
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
||||||
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`).then(() => {
|
||||||
copyTextToClipboard(
|
|
||||||
`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`
|
|
||||||
).then(() => {
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Link Copied!",
|
title: "Link Copied!",
|
||||||
@ -253,9 +240,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
>
|
>
|
||||||
<ContextMenu.Item Icon={ArrowTopRightOnSquareIcon}>
|
<ContextMenu.Item Icon={ArrowTopRightOnSquareIcon}>Open issue in new tab</ContextMenu.Item>
|
||||||
Open issue in new tab
|
|
||||||
</ContextMenu.Item>
|
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
@ -277,9 +262,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
|||||||
{!isNotAllowed && (
|
{!isNotAllowed && (
|
||||||
<div
|
<div
|
||||||
ref={actionSectionRef}
|
ref={actionSectionRef}
|
||||||
className={`z-1 absolute top-1.5 right-1.5 hidden group-hover/card:!flex ${
|
className={`z-1 absolute top-1.5 right-1.5 hidden group-hover/card:!flex ${isMenuActive ? "!flex" : ""}`}
|
||||||
isMenuActive ? "!flex" : ""
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{type && !isNotAllowed && (
|
{type && !isNotAllowed && (
|
||||||
<CustomMenu
|
<CustomMenu
|
||||||
@ -353,11 +336,7 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div className={`flex items-center gap-2 text-xs ${isDropdownActive ? "" : "overflow-x-scroll"}`}>
|
||||||
className={`flex items-center gap-2 text-xs ${
|
|
||||||
isDropdownActive ? "" : "overflow-x-scroll"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{properties.priority && (
|
{properties.priority && (
|
||||||
<ViewPrioritySelect
|
<ViewPrioritySelect
|
||||||
issue={issue}
|
issue={issue}
|
||||||
|
@ -7,7 +7,7 @@ import { mutate } from "swr";
|
|||||||
// react-beautiful-dnd
|
// react-beautiful-dnd
|
||||||
import { DragDropContext, DropResult } from "react-beautiful-dnd";
|
import { DragDropContext, DropResult } from "react-beautiful-dnd";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
||||||
// components
|
// components
|
||||||
@ -17,13 +17,7 @@ import { IssuePeekOverview } from "components/issues";
|
|||||||
import { Spinner } from "components/ui";
|
import { Spinner } from "components/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderDateFormat } from "helpers/date-time.helper";
|
import { renderDateFormat } from "helpers/date-time.helper";
|
||||||
import {
|
import { startOfWeek, lastDayOfWeek, eachDayOfInterval, weekDayInterval, formatDate } from "helpers/calendar.helper";
|
||||||
startOfWeek,
|
|
||||||
lastDayOfWeek,
|
|
||||||
eachDayOfInterval,
|
|
||||||
weekDayInterval,
|
|
||||||
formatDate,
|
|
||||||
} from "helpers/calendar.helper";
|
|
||||||
// types
|
// types
|
||||||
import { ICalendarRange, ICurrentUserResponse, IIssue, UserAuth } from "types";
|
import { ICalendarRange, ICurrentUserResponse, IIssue, UserAuth } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
@ -61,8 +55,7 @@ export const CalendarView: React.FC<Props> = ({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
|
||||||
|
|
||||||
const { calendarIssues, mutateIssues, params, displayFilters, setDisplayFilters } =
|
const { calendarIssues, mutateIssues, params, displayFilters, setDisplayFilters } = useCalendarIssuesView();
|
||||||
useCalendarIssuesView();
|
|
||||||
|
|
||||||
const totalDate = eachDayOfInterval({
|
const totalDate = eachDayOfInterval({
|
||||||
start: calendarDates.startDate,
|
start: calendarDates.startDate,
|
||||||
@ -80,8 +73,7 @@ export const CalendarView: React.FC<Props> = ({
|
|||||||
const filterIssue =
|
const filterIssue =
|
||||||
calendarIssues.length > 0
|
calendarIssues.length > 0
|
||||||
? calendarIssues.filter(
|
? calendarIssues.filter(
|
||||||
(issue) =>
|
(issue) => issue.target_date && renderDateFormat(issue.target_date) === renderDateFormat(date)
|
||||||
issue.target_date && renderDateFormat(issue.target_date) === renderDateFormat(date)
|
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
return {
|
return {
|
||||||
@ -155,18 +147,16 @@ export const CalendarView: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
setDisplayFilters({
|
setDisplayFilters({
|
||||||
calendar_date_range: `${renderDateFormat(startDate)};after,${renderDateFormat(
|
calendar_date_range: `${renderDateFormat(startDate)};after,${renderDateFormat(endDate)};before`,
|
||||||
endDate
|
|
||||||
)};before`,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!displayFilters || displayFilters.calendar_date_range === "")
|
if (!displayFilters || displayFilters.calendar_date_range === "")
|
||||||
setDisplayFilters({
|
setDisplayFilters({
|
||||||
calendar_date_range: `${renderDateFormat(
|
calendar_date_range: `${renderDateFormat(startOfWeek(currentDate))};after,${renderDateFormat(
|
||||||
startOfWeek(currentDate)
|
lastDayOfWeek(currentDate)
|
||||||
)};after,${renderDateFormat(lastDayOfWeek(currentDate))};before`,
|
)};before`,
|
||||||
});
|
});
|
||||||
}, [currentDate, displayFilters, setDisplayFilters]);
|
}, [currentDate, displayFilters, setDisplayFilters]);
|
||||||
|
|
||||||
@ -214,11 +204,7 @@ export const CalendarView: React.FC<Props> = ({
|
|||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>
|
<span>{isMonthlyView ? formatDate(date, "eee").substring(0, 3) : formatDate(date, "eee")}</span>
|
||||||
{isMonthlyView
|
|
||||||
? formatDate(date, "eee").substring(0, 3)
|
|
||||||
: formatDate(date, "eee")}
|
|
||||||
</span>
|
|
||||||
{!isMonthlyView && <span>{formatDate(date, "d")}</span>}
|
{!isMonthlyView && <span>{formatDate(date, "d")}</span>}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
@ -7,7 +7,7 @@ import { mutate } from "swr";
|
|||||||
// react-beautiful-dnd
|
// react-beautiful-dnd
|
||||||
import { DraggableProvided, DraggableStateSnapshot } from "react-beautiful-dnd";
|
import { DraggableProvided, DraggableStateSnapshot } from "react-beautiful-dnd";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
||||||
import useIssuesProperties from "hooks/use-issue-properties";
|
import useIssuesProperties from "hooks/use-issue-properties";
|
||||||
@ -122,13 +122,7 @@ export const SingleCalendarIssue: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
issuesService
|
issuesService
|
||||||
.patchIssue(
|
.patchIssue(workspaceSlug as string, projectId as string, issue.id as string, formData, user)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
issue.id as string,
|
|
||||||
formData,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutate(fetchKey);
|
mutate(fetchKey);
|
||||||
})
|
})
|
||||||
@ -140,11 +134,8 @@ export const SingleCalendarIssue: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleCopyText = () => {
|
const handleCopyText = () => {
|
||||||
const originURL =
|
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
||||||
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`).then(() => {
|
||||||
copyTextToClipboard(
|
|
||||||
`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`
|
|
||||||
).then(() => {
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Link Copied!",
|
title: "Link Copied!",
|
||||||
@ -153,9 +144,7 @@ export const SingleCalendarIssue: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const displayProperties = properties
|
const displayProperties = properties ? Object.values(properties).some((value) => value === true) : false;
|
||||||
? Object.values(properties).some((value) => value === true)
|
|
||||||
: false;
|
|
||||||
|
|
||||||
const openPeekOverview = () => {
|
const openPeekOverview = () => {
|
||||||
const { query } = router;
|
const { query } = router;
|
||||||
|
@ -7,10 +7,10 @@ import useSWR, { mutate } from "swr";
|
|||||||
// react-beautiful-dnd
|
// react-beautiful-dnd
|
||||||
import { DropResult } from "react-beautiful-dnd";
|
import { DropResult } from "react-beautiful-dnd";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
import stateService from "services/state.service";
|
import stateService from "services/project_state.service";
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
import trackEventServices from "services/track-event.service";
|
import trackEventServices from "services/track_event.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
@ -51,10 +51,7 @@ type Props = {
|
|||||||
disableUserActions?: boolean;
|
disableUserActions?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssuesView: React.FC<Props> = ({
|
export const IssuesView: React.FC<Props> = ({ openIssuesListModal, disableUserActions = false }) => {
|
||||||
openIssuesListModal,
|
|
||||||
disableUserActions = false,
|
|
||||||
}) => {
|
|
||||||
// create issue modal
|
// create issue modal
|
||||||
const [createIssueModal, setCreateIssueModal] = useState(false);
|
const [createIssueModal, setCreateIssueModal] = useState(false);
|
||||||
const [createViewModal, setCreateViewModal] = useState<any>(null);
|
const [createViewModal, setCreateViewModal] = useState<any>(null);
|
||||||
@ -64,9 +61,7 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
|
|
||||||
// update issue modal
|
// update issue modal
|
||||||
const [editIssueModal, setEditIssueModal] = useState(false);
|
const [editIssueModal, setEditIssueModal] = useState(false);
|
||||||
const [issueToEdit, setIssueToEdit] = useState<
|
const [issueToEdit, setIssueToEdit] = useState<(IIssue & { actionType: "edit" | "delete" }) | undefined>(undefined);
|
||||||
(IIssue & { actionType: "edit" | "delete" }) | undefined
|
|
||||||
>(undefined);
|
|
||||||
|
|
||||||
// delete issue modal
|
// delete issue modal
|
||||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||||
@ -87,15 +82,12 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { groupedByIssues, mutateIssues, displayFilters, filters, isEmpty, setFilters, params } =
|
const { groupedByIssues, mutateIssues, displayFilters, filters, isEmpty, setFilters, params } = useIssuesView();
|
||||||
useIssuesView();
|
|
||||||
const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string);
|
const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string);
|
||||||
|
|
||||||
const { data: stateGroups } = useSWR(
|
const { data: stateGroups } = useSWR(
|
||||||
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
||||||
workspaceSlug
|
workspaceSlug ? () => stateService.getStates(workspaceSlug as string, projectId as string) : null
|
||||||
? () => stateService.getStates(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
const states = getStatesList(stateGroups);
|
const states = getStatesList(stateGroups);
|
||||||
|
|
||||||
@ -141,12 +133,10 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
// check if dropping in the same group
|
// check if dropping in the same group
|
||||||
if (source.droppableId === destination.droppableId) {
|
if (source.droppableId === destination.droppableId) {
|
||||||
// check if dropping at beginning
|
// check if dropping at beginning
|
||||||
if (destination.index === 0)
|
if (destination.index === 0) newSortOrder = destinationGroupArray[0].sort_order - 10000;
|
||||||
newSortOrder = destinationGroupArray[0].sort_order - 10000;
|
|
||||||
// check if dropping at last
|
// check if dropping at last
|
||||||
else if (destination.index === destinationGroupArray.length - 1)
|
else if (destination.index === destinationGroupArray.length - 1)
|
||||||
newSortOrder =
|
newSortOrder = destinationGroupArray[destinationGroupArray.length - 1].sort_order + 10000;
|
||||||
destinationGroupArray[destinationGroupArray.length - 1].sort_order + 10000;
|
|
||||||
else {
|
else {
|
||||||
if (destination.index > source.index)
|
if (destination.index > source.index)
|
||||||
newSortOrder =
|
newSortOrder =
|
||||||
@ -161,12 +151,10 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// check if dropping at beginning
|
// check if dropping at beginning
|
||||||
if (destination.index === 0)
|
if (destination.index === 0) newSortOrder = destinationGroupArray[0].sort_order - 10000;
|
||||||
newSortOrder = destinationGroupArray[0].sort_order - 10000;
|
|
||||||
// check if dropping at last
|
// check if dropping at last
|
||||||
else if (destination.index === destinationGroupArray.length)
|
else if (destination.index === destinationGroupArray.length)
|
||||||
newSortOrder =
|
newSortOrder = destinationGroupArray[destinationGroupArray.length - 1].sort_order + 10000;
|
||||||
destinationGroupArray[destinationGroupArray.length - 1].sort_order + 10000;
|
|
||||||
else
|
else
|
||||||
newSortOrder =
|
newSortOrder =
|
||||||
(destinationGroupArray[destination.index - 1].sort_order +
|
(destinationGroupArray[destination.index - 1].sort_order +
|
||||||
@ -180,18 +168,14 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
|
|
||||||
const destinationGroup = destination.droppableId; // destination group id
|
const destinationGroup = destination.droppableId; // destination group id
|
||||||
|
|
||||||
if (
|
if (displayFilters.order_by === "sort_order" || source.droppableId !== destination.droppableId) {
|
||||||
displayFilters.order_by === "sort_order" ||
|
|
||||||
source.droppableId !== destination.droppableId
|
|
||||||
) {
|
|
||||||
// different group/column;
|
// different group/column;
|
||||||
|
|
||||||
// source.droppableId !== destination.droppableId -> even if order by is not sort_order,
|
// source.droppableId !== destination.droppableId -> even if order by is not sort_order,
|
||||||
// if the issue is moved to a different group, then we will change the group of the
|
// if the issue is moved to a different group, then we will change the group of the
|
||||||
// dragged item(or issue)
|
// dragged item(or issue)
|
||||||
|
|
||||||
if (displayFilters.group_by === "priority")
|
if (displayFilters.group_by === "priority") draggedItem.priority = destinationGroup as TIssuePriorities;
|
||||||
draggedItem.priority = destinationGroup as TIssuePriorities;
|
|
||||||
else if (displayFilters.group_by === "state") {
|
else if (displayFilters.group_by === "state") {
|
||||||
draggedItem.state = destinationGroup;
|
draggedItem.state = destinationGroup;
|
||||||
draggedItem.state_detail = states?.find((s) => s.id === destinationGroup) as IState;
|
draggedItem.state_detail = states?.find((s) => s.id === destinationGroup) as IState;
|
||||||
@ -219,14 +203,8 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...prevData,
|
...prevData,
|
||||||
[sourceGroup]: orderArrayBy(
|
[sourceGroup]: orderArrayBy(sourceGroupArray, displayFilters.order_by ?? "-created_at"),
|
||||||
sourceGroupArray,
|
[destinationGroup]: orderArrayBy(destinationGroupArray, displayFilters.order_by ?? "-created_at"),
|
||||||
displayFilters.order_by ?? "-created_at"
|
|
||||||
),
|
|
||||||
[destinationGroup]: orderArrayBy(
|
|
||||||
destinationGroupArray,
|
|
||||||
displayFilters.order_by ?? "-created_at"
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
@ -246,14 +224,9 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
user
|
user
|
||||||
)
|
)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
const sourceStateBeforeDrag = states?.find(
|
const sourceStateBeforeDrag = states?.find((state) => state.name === source.droppableId);
|
||||||
(state) => state.name === source.droppableId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (sourceStateBeforeDrag?.group !== "completed" && response?.state_detail?.group === "completed")
|
||||||
sourceStateBeforeDrag?.group !== "completed" &&
|
|
||||||
response?.state_detail?.group === "completed"
|
|
||||||
)
|
|
||||||
trackEventServices.trackIssueMarkedAsDoneEvent(
|
trackEventServices.trackIssueMarkedAsDoneEvent(
|
||||||
{
|
{
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
@ -387,12 +360,7 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
issuesService
|
issuesService
|
||||||
.removeIssueFromCycle(
|
.removeIssueFromCycle(workspaceSlug as string, projectId as string, cycleId as string, bridgeId)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
cycleId as string,
|
|
||||||
bridgeId
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
@ -430,12 +398,7 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
modulesService
|
modulesService
|
||||||
.removeIssueFromModule(
|
.removeIssueFromModule(workspaceSlug as string, projectId as string, moduleId as string, bridgeId)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
moduleId as string,
|
|
||||||
bridgeId
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
title: "Success",
|
title: "Success",
|
||||||
@ -450,12 +413,9 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
[displayFilters.group_by, workspaceSlug, projectId, moduleId, params, setToastAlert]
|
[displayFilters.group_by, workspaceSlug, projectId, moduleId, params, setToastAlert]
|
||||||
);
|
);
|
||||||
|
|
||||||
const nullFilters = Object.keys(filters).filter(
|
const nullFilters = Object.keys(filters).filter((key) => filters[key as keyof IIssueFilterOptions] === null);
|
||||||
(key) => filters[key as keyof IIssueFilterOptions] === null
|
|
||||||
);
|
|
||||||
|
|
||||||
const areFiltersApplied =
|
const areFiltersApplied = Object.keys(filters).length > 0 && nullFilters.length !== Object.keys(filters).length;
|
||||||
Object.keys(filters).length > 0 && nullFilters.length !== Object.keys(filters).length;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -581,10 +541,7 @@ export const IssuesView: React.FC<Props> = ({
|
|||||||
: undefined,
|
: undefined,
|
||||||
secondaryButton:
|
secondaryButton:
|
||||||
cycleId || moduleId ? (
|
cycleId || moduleId ? (
|
||||||
<SecondaryButton
|
<SecondaryButton className="flex items-center gap-1.5" onClick={openIssuesListModal ?? (() => {})}>
|
||||||
className="flex items-center gap-1.5"
|
|
||||||
onClick={openIssuesListModal ?? (() => {})}
|
|
||||||
>
|
|
||||||
<PlusIcon className="h-4 w-4" />
|
<PlusIcon className="h-4 w-4" />
|
||||||
Add an existing issue
|
Add an existing issue
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
|
@ -5,7 +5,7 @@ import { useRouter } from "next/router";
|
|||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
@ -45,12 +45,7 @@ import {
|
|||||||
UserAuth,
|
UserAuth,
|
||||||
} from "types";
|
} from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import { CYCLE_DETAILS, MODULE_DETAILS, SUB_ISSUES, USER_PROFILE_PROJECT_SEGREGATION } from "constants/fetch-keys";
|
||||||
CYCLE_DETAILS,
|
|
||||||
MODULE_DETAILS,
|
|
||||||
SUB_ISSUES,
|
|
||||||
USER_PROFILE_PROJECT_SEGREGATION,
|
|
||||||
} from "constants/fetch-keys";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
type?: string;
|
type?: string;
|
||||||
@ -140,39 +135,24 @@ export const SingleListIssue: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
issuesService
|
issuesService.patchIssue(workspaceSlug as string, issue.project, issue.id, formData, user).then(() => {
|
||||||
.patchIssue(workspaceSlug as string, issue.project, issue.id, formData, user)
|
mutateIssues();
|
||||||
.then(() => {
|
|
||||||
mutateIssues();
|
|
||||||
|
|
||||||
if (userId)
|
if (userId)
|
||||||
mutate<IUserProfileProjectSegregation>(
|
mutate<IUserProfileProjectSegregation>(
|
||||||
USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString())
|
USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString())
|
||||||
);
|
);
|
||||||
|
|
||||||
if (cycleId) mutate(CYCLE_DETAILS(cycleId as string));
|
if (cycleId) mutate(CYCLE_DETAILS(cycleId as string));
|
||||||
if (moduleId) mutate(MODULE_DETAILS(moduleId as string));
|
if (moduleId) mutate(MODULE_DETAILS(moduleId as string));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[displayFilters, workspaceSlug, cycleId, moduleId, userId, groupTitle, index, mutateIssues, user]
|
||||||
displayFilters,
|
|
||||||
workspaceSlug,
|
|
||||||
cycleId,
|
|
||||||
moduleId,
|
|
||||||
userId,
|
|
||||||
groupTitle,
|
|
||||||
index,
|
|
||||||
mutateIssues,
|
|
||||||
user,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCopyText = () => {
|
const handleCopyText = () => {
|
||||||
const originURL =
|
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
||||||
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`).then(() => {
|
||||||
copyTextToClipboard(
|
|
||||||
`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`
|
|
||||||
).then(() => {
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Link Copied!",
|
title: "Link Copied!",
|
||||||
@ -197,8 +177,7 @@ export const SingleListIssue: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const isNotAllowed =
|
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disableUserActions || isArchivedIssues;
|
||||||
userAuth.isGuest || userAuth.isViewer || disableUserActions || isArchivedIssues;
|
|
||||||
|
|
||||||
console.log("properties", properties);
|
console.log("properties", properties);
|
||||||
|
|
||||||
@ -243,9 +222,7 @@ export const SingleListIssue: React.FC<Props> = ({
|
|||||||
Copy issue link
|
Copy issue link
|
||||||
</ContextMenu.Item>
|
</ContextMenu.Item>
|
||||||
<a href={issuePath} target="_blank" rel="noreferrer noopener">
|
<a href={issuePath} target="_blank" rel="noreferrer noopener">
|
||||||
<ContextMenu.Item Icon={ArrowTopRightOnSquareIcon}>
|
<ContextMenu.Item Icon={ArrowTopRightOnSquareIcon}>Open issue in new tab</ContextMenu.Item>
|
||||||
Open issue in new tab
|
|
||||||
</ContextMenu.Item>
|
|
||||||
</a>
|
</a>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -286,11 +263,7 @@ export const SingleListIssue: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div className={`flex flex-shrink-0 items-center gap-2 text-xs ${isArchivedIssues ? "opacity-60" : ""}`}>
|
||||||
className={`flex flex-shrink-0 items-center gap-2 text-xs ${
|
|
||||||
isArchivedIssues ? "opacity-60" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{properties.priority && (
|
{properties.priority && (
|
||||||
<ViewPrioritySelect
|
<ViewPrioritySelect
|
||||||
issue={issue}
|
issue={issue}
|
||||||
|
@ -5,7 +5,7 @@ import useSWR from "swr";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Disclosure, Transition } from "@headlessui/react";
|
import { Disclosure, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useProjects from "hooks/use-projects";
|
import useProjects from "hooks/use-projects";
|
||||||
@ -77,9 +77,7 @@ export const SingleList: React.FC<Props> = ({
|
|||||||
|
|
||||||
const { data: issueLabels } = useSWR<IIssueLabels[]>(
|
const { data: issueLabels } = useSWR<IIssueLabels[]>(
|
||||||
workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null,
|
workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? () => issuesService.getIssueLabels(workspaceSlug as string, projectId as string) : null
|
||||||
? () => issuesService.getIssueLabels(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: members } = useSWR(
|
const { data: members } = useSWR(
|
||||||
@ -120,12 +118,7 @@ export const SingleList: React.FC<Props> = ({
|
|||||||
switch (displayFilters?.group_by) {
|
switch (displayFilters?.group_by) {
|
||||||
case "state":
|
case "state":
|
||||||
icon = currentState && (
|
icon = currentState && (
|
||||||
<StateGroupIcon
|
<StateGroupIcon stateGroup={currentState.group} color={currentState.color} height="16px" width="16px" />
|
||||||
stateGroup={currentState.group}
|
|
||||||
color={currentState.color}
|
|
||||||
height="16px"
|
|
||||||
width="16px"
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "state_detail.group":
|
case "state_detail.group":
|
||||||
@ -152,14 +145,8 @@ export const SingleList: React.FC<Props> = ({
|
|||||||
: null);
|
: null);
|
||||||
break;
|
break;
|
||||||
case "labels":
|
case "labels":
|
||||||
const labelColor =
|
const labelColor = issueLabels?.find((label) => label.id === groupTitle)?.color ?? "#000000";
|
||||||
issueLabels?.find((label) => label.id === groupTitle)?.color ?? "#000000";
|
icon = <span className="h-3 w-3 flex-shrink-0 rounded-full" style={{ backgroundColor: labelColor }} />;
|
||||||
icon = (
|
|
||||||
<span
|
|
||||||
className="h-3 w-3 flex-shrink-0 rounded-full"
|
|
||||||
style={{ backgroundColor: labelColor }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case "assignees":
|
case "assignees":
|
||||||
case "created_by":
|
case "created_by":
|
||||||
@ -181,9 +168,7 @@ export const SingleList: React.FC<Props> = ({
|
|||||||
<div className="flex items-center justify-between px-4 py-2.5 bg-custom-background-90">
|
<div className="flex items-center justify-between px-4 py-2.5 bg-custom-background-90">
|
||||||
<Disclosure.Button>
|
<Disclosure.Button>
|
||||||
<div className="flex items-center gap-x-3">
|
<div className="flex items-center gap-x-3">
|
||||||
{displayFilters?.group_by !== null && (
|
{displayFilters?.group_by !== null && <div className="flex items-center">{getGroupIcon()}</div>}
|
||||||
<div className="flex items-center">{getGroupIcon()}</div>
|
|
||||||
)}
|
|
||||||
{displayFilters?.group_by !== null ? (
|
{displayFilters?.group_by !== null ? (
|
||||||
<h2
|
<h2
|
||||||
className={`text-sm font-semibold leading-6 text-custom-text-100 ${
|
className={`text-sm font-semibold leading-6 text-custom-text-100 ${
|
||||||
@ -226,9 +211,7 @@ export const SingleList: React.FC<Props> = ({
|
|||||||
>
|
>
|
||||||
<CustomMenu.MenuItem onClick={addIssueToGroup}>Create new</CustomMenu.MenuItem>
|
<CustomMenu.MenuItem onClick={addIssueToGroup}>Create new</CustomMenu.MenuItem>
|
||||||
{openIssuesListModal && (
|
{openIssuesListModal && (
|
||||||
<CustomMenu.MenuItem onClick={openIssuesListModal}>
|
<CustomMenu.MenuItem onClick={openIssuesListModal}>Add an existing issue</CustomMenu.MenuItem>
|
||||||
Add an existing issue
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
)}
|
)}
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
)}
|
)}
|
||||||
@ -256,19 +239,14 @@ export const SingleList: React.FC<Props> = ({
|
|||||||
makeIssueCopy={() => handleIssueAction(issue, "copy")}
|
makeIssueCopy={() => handleIssueAction(issue, "copy")}
|
||||||
handleDeleteIssue={() => handleIssueAction(issue, "delete")}
|
handleDeleteIssue={() => handleIssueAction(issue, "delete")}
|
||||||
handleDraftIssueSelect={
|
handleDraftIssueSelect={
|
||||||
handleDraftIssueAction
|
handleDraftIssueAction ? () => handleDraftIssueAction(issue, "edit") : undefined
|
||||||
? () => handleDraftIssueAction(issue, "edit")
|
|
||||||
: undefined
|
|
||||||
}
|
}
|
||||||
handleDraftIssueDelete={
|
handleDraftIssueDelete={
|
||||||
handleDraftIssueAction
|
handleDraftIssueAction ? () => handleDraftIssueAction(issue, "delete") : undefined
|
||||||
? () => handleDraftIssueAction(issue, "delete")
|
|
||||||
: undefined
|
|
||||||
}
|
}
|
||||||
handleMyIssueOpen={handleMyIssueOpen}
|
handleMyIssueOpen={handleMyIssueOpen}
|
||||||
removeIssue={() => {
|
removeIssue={() => {
|
||||||
if (removeIssue !== null && issue.bridge_id)
|
if (removeIssue !== null && issue.bridge_id) removeIssue(issue.bridge_id, issue.id);
|
||||||
removeIssue(issue.bridge_id, issue.id);
|
|
||||||
}}
|
}}
|
||||||
disableUserActions={disableUserActions}
|
disableUserActions={disableUserActions}
|
||||||
user={user}
|
user={user}
|
||||||
@ -277,9 +255,7 @@ export const SingleList: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="bg-custom-background-100 px-4 py-2.5 text-sm text-custom-text-200">
|
<p className="bg-custom-background-100 px-4 py-2.5 text-sm text-custom-text-200">No issues.</p>
|
||||||
No issues.
|
|
||||||
</p>
|
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<div className="flex h-full w-full items-center justify-center">Loading...</div>
|
<div className="flex h-full w-full items-center justify-center">Loading...</div>
|
||||||
|
@ -17,17 +17,12 @@ import {
|
|||||||
import { Popover2 } from "@blueprintjs/popover2";
|
import { Popover2 } from "@blueprintjs/popover2";
|
||||||
// icons
|
// icons
|
||||||
import { Icon } from "components/ui";
|
import { Icon } from "components/ui";
|
||||||
import {
|
import { EllipsisHorizontalIcon, LinkIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||||
EllipsisHorizontalIcon,
|
|
||||||
LinkIcon,
|
|
||||||
PencilIcon,
|
|
||||||
TrashIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
// hooks
|
// hooks
|
||||||
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
|
import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// constant
|
// constant
|
||||||
import {
|
import {
|
||||||
CYCLE_DETAILS,
|
CYCLE_DETAILS,
|
||||||
@ -133,13 +128,7 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
issuesService
|
issuesService
|
||||||
.patchIssue(
|
.patchIssue(workspaceSlug as string, projectId as string, issue.id as string, formData, user)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
issue.id as string,
|
|
||||||
formData,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (issue.parent) {
|
if (issue.parent) {
|
||||||
mutate(SUB_ISSUES(issue.parent as string));
|
mutate(SUB_ISSUES(issue.parent as string));
|
||||||
@ -167,11 +156,8 @@ export const SingleSpreadsheetIssue: React.FC<Props> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyText = () => {
|
const handleCopyText = () => {
|
||||||
const originURL =
|
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
||||||
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`).then(() => {
|
||||||
copyTextToClipboard(
|
|
||||||
`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`
|
|
||||||
).then(() => {
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Link Copied!",
|
title: "Link Copied!",
|
||||||
|
@ -9,7 +9,7 @@ import { useForm } from "react-hook-form";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import estimatesService from "services/estimates.service";
|
import estimatesService from "services/project_estimates.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
@ -119,13 +119,7 @@ export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data,
|
|||||||
);
|
);
|
||||||
|
|
||||||
await estimatesService
|
await estimatesService
|
||||||
.patchEstimate(
|
.patchEstimate(workspaceSlug as string, projectId as string, data?.id as string, payload, user)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
data?.id as string,
|
|
||||||
payload,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutate(ESTIMATES_LIST(projectId.toString()));
|
mutate(ESTIMATES_LIST(projectId.toString()));
|
||||||
mutate(ESTIMATE_DETAILS(data.id));
|
mutate(ESTIMATE_DETAILS(data.id));
|
||||||
@ -257,9 +251,7 @@ export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data,
|
|||||||
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
|
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="text-lg font-medium leading-6">
|
<div className="text-lg font-medium leading-6">{data ? "Update" : "Create"} Estimate</div>
|
||||||
{data ? "Update" : "Create"} Estimate
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
id="name"
|
id="name"
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import CSVIntegrationService from "services/integration/csv.services";
|
import CSVIntegrationService from "services/csv.services";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
@ -23,13 +22,9 @@ type Props = {
|
|||||||
mutateServices: () => void;
|
mutateServices: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Exporter: React.FC<Props> = ({
|
const cvsService = new CSVIntegrationService();
|
||||||
isOpen,
|
|
||||||
handleClose,
|
export const Exporter: React.FC<Props> = ({ isOpen, handleClose, user, provider, mutateServices }) => {
|
||||||
user,
|
|
||||||
provider,
|
|
||||||
mutateServices,
|
|
||||||
}) => {
|
|
||||||
const [exportLoading, setExportLoading] = useState(false);
|
const [exportLoading, setExportLoading] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
@ -60,7 +55,8 @@ export const Exporter: React.FC<Props> = ({
|
|||||||
project: value,
|
project: value,
|
||||||
multiple: multiple,
|
multiple: multiple,
|
||||||
};
|
};
|
||||||
await CSVIntegrationService.exportCSVService(workspaceSlug as string, payload, user)
|
await cvsService
|
||||||
|
.exportCSVService(workspaceSlug as string, payload, user)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutateServices();
|
mutateServices();
|
||||||
router.push(`/${workspaceSlug}/settings/exports`);
|
router.push(`/${workspaceSlug}/settings/exports`);
|
||||||
@ -69,13 +65,7 @@ export const Exporter: React.FC<Props> = ({
|
|||||||
type: "success",
|
type: "success",
|
||||||
title: "Export Successful",
|
title: "Export Successful",
|
||||||
message: `You will be able to download the exported ${
|
message: `You will be able to download the exported ${
|
||||||
provider === "csv"
|
provider === "csv" ? "CSV" : provider === "xlsx" ? "Excel" : provider === "json" ? "JSON" : ""
|
||||||
? "CSV"
|
|
||||||
: provider === "xlsx"
|
|
||||||
? "Excel"
|
|
||||||
: provider === "json"
|
|
||||||
? "JSON"
|
|
||||||
: ""
|
|
||||||
} from the previous export.`,
|
} from the previous export.`,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@ -122,13 +112,7 @@ export const Exporter: React.FC<Props> = ({
|
|||||||
<span className="flex items-center justify-start">
|
<span className="flex items-center justify-start">
|
||||||
<h3 className="text-xl font-medium 2xl:text-2xl">
|
<h3 className="text-xl font-medium 2xl:text-2xl">
|
||||||
Export to{" "}
|
Export to{" "}
|
||||||
{provider === "csv"
|
{provider === "csv" ? "CSV" : provider === "xlsx" ? "Excel" : provider === "json" ? "JSON" : ""}
|
||||||
? "CSV"
|
|
||||||
: provider === "xlsx"
|
|
||||||
? "Excel"
|
|
||||||
: provider === "json"
|
|
||||||
? "JSON"
|
|
||||||
: ""}
|
|
||||||
</h3>
|
</h3>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -155,22 +139,12 @@ export const Exporter: React.FC<Props> = ({
|
|||||||
onClick={() => setMultiple(!multiple)}
|
onClick={() => setMultiple(!multiple)}
|
||||||
className="flex items-center gap-2 max-w-min cursor-pointer"
|
className="flex items-center gap-2 max-w-min cursor-pointer"
|
||||||
>
|
>
|
||||||
<input
|
<input type="checkbox" checked={multiple} onChange={() => setMultiple(!multiple)} />
|
||||||
type="checkbox"
|
<div className="text-sm whitespace-nowrap">Export the data into separate files</div>
|
||||||
checked={multiple}
|
|
||||||
onChange={() => setMultiple(!multiple)}
|
|
||||||
/>
|
|
||||||
<div className="text-sm whitespace-nowrap">
|
|
||||||
Export the data into separate files
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
|
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
|
||||||
<PrimaryButton
|
<PrimaryButton onClick={ExportCSVToMail} disabled={exportLoading} loading={exportLoading}>
|
||||||
onClick={ExportCSVToMail}
|
|
||||||
disabled={exportLoading}
|
|
||||||
loading={exportLoading}
|
|
||||||
>
|
|
||||||
{exportLoading ? "Exporting..." : "Export"}
|
{exportLoading ? "Exporting..." : "Export"}
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@ import useSWR, { mutate } from "swr";
|
|||||||
// hooks
|
// hooks
|
||||||
import useUserAuth from "hooks/use-user-auth";
|
import useUserAuth from "hooks/use-user-auth";
|
||||||
// services
|
// services
|
||||||
import IntegrationService from "services/integration";
|
import IntegrationService from "services/integration.service";
|
||||||
// components
|
// components
|
||||||
import { Exporter, SingleExport } from "components/exporter";
|
import { Exporter, SingleExport } from "components/exporter";
|
||||||
// ui
|
// ui
|
||||||
@ -32,9 +32,7 @@ const IntegrationGuide = () => {
|
|||||||
const { user } = useUserAuth();
|
const { user } = useUserAuth();
|
||||||
|
|
||||||
const { data: exporterServices } = useSWR(
|
const { data: exporterServices } = useSWR(
|
||||||
workspaceSlug && cursor
|
workspaceSlug && cursor ? EXPORT_SERVICES_LIST(workspaceSlug as string, cursor, `${per_page}`) : null,
|
||||||
? EXPORT_SERVICES_LIST(workspaceSlug as string, cursor, `${per_page}`)
|
|
||||||
: null,
|
|
||||||
workspaceSlug && cursor
|
workspaceSlug && cursor
|
||||||
? () => IntegrationService.getExportsServicesList(workspaceSlug as string, cursor, per_page)
|
? () => IntegrationService.getExportsServicesList(workspaceSlug as string, cursor, per_page)
|
||||||
: null
|
: null
|
||||||
@ -57,20 +55,11 @@ const IntegrationGuide = () => {
|
|||||||
<div className="flex items-start justify-between gap-4 w-full">
|
<div className="flex items-start justify-between gap-4 w-full">
|
||||||
<div className="flex item-center gap-2.5">
|
<div className="flex item-center gap-2.5">
|
||||||
<div className="relative h-10 w-10 flex-shrink-0">
|
<div className="relative h-10 w-10 flex-shrink-0">
|
||||||
<Image
|
<Image src={service.logo} layout="fill" objectFit="cover" alt={`${service.title} Logo`} />
|
||||||
src={service.logo}
|
|
||||||
layout="fill"
|
|
||||||
objectFit="cover"
|
|
||||||
alt={`${service.title} Logo`}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="flex items-center gap-4 text-sm font-medium">
|
<h3 className="flex items-center gap-4 text-sm font-medium">{service.title}</h3>
|
||||||
{service.title}
|
<p className="text-sm text-custom-text-200 tracking-tight">{service.description}</p>
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-custom-text-200 tracking-tight">
|
|
||||||
{service.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
@ -96,9 +85,9 @@ const IntegrationGuide = () => {
|
|||||||
className="flex flex-shrink-0 items-center gap-1 rounded bg-custom-background-80 py-1 px-1.5 text-xs outline-none"
|
className="flex flex-shrink-0 items-center gap-1 rounded bg-custom-background-80 py-1 px-1.5 text-xs outline-none"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
mutate(
|
mutate(EXPORT_SERVICES_LIST(workspaceSlug as string, `${cursor}`, `${per_page}`)).then(() =>
|
||||||
EXPORT_SERVICES_LIST(workspaceSlug as string, `${cursor}`, `${per_page}`)
|
setRefreshing(false)
|
||||||
).then(() => setRefreshing(false));
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ArrowPathIcon className={`h-3 w-3 ${refreshing ? "animate-spin" : ""}`} />{" "}
|
<ArrowPathIcon className={`h-3 w-3 ${refreshing ? "animate-spin" : ""}`} />{" "}
|
||||||
@ -108,9 +97,7 @@ const IntegrationGuide = () => {
|
|||||||
<div className="flex gap-2 items-center text-xs">
|
<div className="flex gap-2 items-center text-xs">
|
||||||
<button
|
<button
|
||||||
disabled={!exporterServices?.prev_page_results}
|
disabled={!exporterServices?.prev_page_results}
|
||||||
onClick={() =>
|
onClick={() => exporterServices?.prev_page_results && setCursor(exporterServices?.prev_cursor)}
|
||||||
exporterServices?.prev_page_results && setCursor(exporterServices?.prev_cursor)
|
|
||||||
}
|
|
||||||
className={`flex items-center border border-custom-primary-100 text-custom-primary-100 px-1 rounded ${
|
className={`flex items-center border border-custom-primary-100 text-custom-primary-100 px-1 rounded ${
|
||||||
exporterServices?.prev_page_results
|
exporterServices?.prev_page_results
|
||||||
? "cursor-pointer hover:bg-custom-primary-100 hover:text-white"
|
? "cursor-pointer hover:bg-custom-primary-100 hover:text-white"
|
||||||
@ -122,9 +109,7 @@ const IntegrationGuide = () => {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
disabled={!exporterServices?.next_page_results}
|
disabled={!exporterServices?.next_page_results}
|
||||||
onClick={() =>
|
onClick={() => exporterServices?.next_page_results && setCursor(exporterServices?.next_cursor)}
|
||||||
exporterServices?.next_page_results && setCursor(exporterServices?.next_cursor)
|
|
||||||
}
|
|
||||||
className={`flex items-center border border-custom-primary-100 text-custom-primary-100 px-1 rounded ${
|
className={`flex items-center border border-custom-primary-100 text-custom-primary-100 px-1 rounded ${
|
||||||
exporterServices?.next_page_results
|
exporterServices?.next_page_results
|
||||||
? "cursor-pointer hover:bg-custom-primary-100 hover:text-white"
|
? "cursor-pointer hover:bg-custom-primary-100 hover:text-white"
|
||||||
@ -147,9 +132,7 @@ const IntegrationGuide = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-custom-text-200 px-4 py-6">
|
<p className="text-sm text-custom-text-200 px-4 py-6">No previous export available.</p>
|
||||||
No previous export available.
|
|
||||||
</p>
|
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<Loader className="mt-6 grid grid-cols-1 gap-3">
|
<Loader className="mt-6 grid grid-cols-1 gap-3">
|
||||||
@ -169,9 +152,7 @@ const IntegrationGuide = () => {
|
|||||||
data={null}
|
data={null}
|
||||||
user={user}
|
user={user}
|
||||||
provider={provider}
|
provider={provider}
|
||||||
mutateServices={() =>
|
mutateServices={() => mutate(EXPORT_SERVICES_LIST(workspaceSlug as string, `${cursor}`, `${per_page}`))}
|
||||||
mutate(EXPORT_SERVICES_LIST(workspaceSlug as string, `${cursor}`, `${per_page}`))
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { KeyedMutator } from "swr";
|
import { KeyedMutator } from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IIssue } from "types";
|
import { ICurrentUserResponse, IIssue } from "types";
|
||||||
import { IBlockUpdateData } from "../types";
|
import { IBlockUpdateData } from "../types";
|
||||||
@ -34,8 +34,7 @@ export const updateGanttIssue = (
|
|||||||
|
|
||||||
const newPayload: any = { ...payload };
|
const newPayload: any = { ...payload };
|
||||||
|
|
||||||
if (newPayload.sort_order && payload.sort_order)
|
if (newPayload.sort_order && payload.sort_order) newPayload.sort_order = payload.sort_order.newSortOrder;
|
||||||
newPayload.sort_order = payload.sort_order.newSortOrder;
|
|
||||||
|
|
||||||
issuesService.patchIssue(workspaceSlug, issue.project, issue.id, newPayload, user);
|
issuesService.patchIssue(workspaceSlug, issue.project, issue.id, newPayload, user);
|
||||||
};
|
};
|
||||||
|
@ -5,7 +5,7 @@ import useSWR, { mutate } from "swr";
|
|||||||
// components
|
// components
|
||||||
import { AddComment, IssueActivitySection } from "components/issues";
|
import { AddComment, IssueActivitySection } from "components/issues";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
@ -25,16 +25,9 @@ export const InboxIssueActivity: React.FC<Props> = ({ issueDetails }) => {
|
|||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
|
||||||
const { data: issueActivity, mutate: mutateIssueActivity } = useSWR(
|
const { data: issueActivity, mutate: mutateIssueActivity } = useSWR(
|
||||||
|
workspaceSlug && projectId && inboxIssueId ? PROJECT_ISSUES_ACTIVITY(inboxIssueId.toString()) : null,
|
||||||
workspaceSlug && projectId && inboxIssueId
|
workspaceSlug && projectId && inboxIssueId
|
||||||
? PROJECT_ISSUES_ACTIVITY(inboxIssueId.toString())
|
? () => issuesService.getIssueActivities(workspaceSlug.toString(), projectId.toString(), inboxIssueId.toString())
|
||||||
: null,
|
|
||||||
workspaceSlug && projectId && inboxIssueId
|
|
||||||
? () =>
|
|
||||||
issuesService.getIssueActivities(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
inboxIssueId.toString()
|
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -42,14 +35,7 @@ export const InboxIssueActivity: React.FC<Props> = ({ issueDetails }) => {
|
|||||||
if (!workspaceSlug || !projectId || !inboxIssueId) return;
|
if (!workspaceSlug || !projectId || !inboxIssueId) return;
|
||||||
|
|
||||||
await issuesService
|
await issuesService
|
||||||
.patchIssueComment(
|
.patchIssueComment(workspaceSlug as string, projectId as string, inboxIssueId as string, commentId, data, user)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
inboxIssueId as string,
|
|
||||||
commentId,
|
|
||||||
data,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => mutateIssueActivity());
|
.then(() => mutateIssueActivity());
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -59,13 +45,7 @@ export const InboxIssueActivity: React.FC<Props> = ({ issueDetails }) => {
|
|||||||
mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false);
|
mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false);
|
||||||
|
|
||||||
await issuesService
|
await issuesService
|
||||||
.deleteIssueComment(
|
.deleteIssueComment(workspaceSlug as string, projectId as string, inboxIssueId as string, commentId, user)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
inboxIssueId as string,
|
|
||||||
commentId,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => mutateIssueActivity());
|
.then(() => mutateIssueActivity());
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -73,13 +53,7 @@ export const InboxIssueActivity: React.FC<Props> = ({ issueDetails }) => {
|
|||||||
if (!workspaceSlug || !issueDetails) return;
|
if (!workspaceSlug || !issueDetails) return;
|
||||||
|
|
||||||
await issuesService
|
await issuesService
|
||||||
.createIssueComment(
|
.createIssueComment(workspaceSlug.toString(), issueDetails.project, issueDetails.id, formData, user)
|
||||||
workspaceSlug.toString(),
|
|
||||||
issueDetails.project,
|
|
||||||
issueDetails.id,
|
|
||||||
formData,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutate(PROJECT_ISSUES_ACTIVITY(issueDetails.id));
|
mutate(PROJECT_ISSUES_ACTIVITY(issueDetails.id));
|
||||||
})
|
})
|
||||||
|
@ -11,7 +11,7 @@ import { Combobox, Dialog, Transition } from "@headlessui/react";
|
|||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// services
|
// services
|
||||||
import issuesServices from "services/issues.service";
|
import issuesServices from "services/issue.service";
|
||||||
// ui
|
// ui
|
||||||
import { PrimaryButton, SecondaryButton } from "components/ui";
|
import { PrimaryButton, SecondaryButton } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
@ -39,9 +39,7 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
|
|||||||
const { workspaceSlug, projectId, issueId } = router.query;
|
const { workspaceSlug, projectId, issueId } = router.query;
|
||||||
|
|
||||||
const { data: issues } = useSWR(
|
const { data: issues } = useSWR(
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) : null,
|
||||||
? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string)
|
|
||||||
: null,
|
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () =>
|
? () =>
|
||||||
issuesServices
|
issuesServices
|
||||||
@ -71,8 +69,7 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
|
|||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredIssues =
|
const filteredIssues = (query === "" ? issues : issues?.filter((issue) => issue.name.includes(query))) ?? [];
|
||||||
(query === "" ? issues : issues?.filter((issue) => issue.name.includes(query))) ?? [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={isOpen} as={React.Fragment} afterLeave={() => setQuery("")} appear>
|
<Transition.Root show={isOpen} as={React.Fragment} afterLeave={() => setQuery("")} appear>
|
||||||
@ -128,9 +125,7 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
|
|||||||
{filteredIssues.length > 0 ? (
|
{filteredIssues.length > 0 ? (
|
||||||
<li className="p-2">
|
<li className="p-2">
|
||||||
{query === "" && (
|
{query === "" && (
|
||||||
<h2 className="mt-4 mb-2 px-3 text-xs font-semibold text-custom-text-100">
|
<h2 className="mt-4 mb-2 px-3 text-xs font-semibold text-custom-text-100">Select issue</h2>
|
||||||
Select issue
|
|
||||||
</h2>
|
|
||||||
)}
|
)}
|
||||||
<ul className="text-sm text-custom-text-100">
|
<ul className="text-sm text-custom-text-100">
|
||||||
{filteredIssues.map((issue) => (
|
{filteredIssues.map((issue) => (
|
||||||
@ -140,9 +135,7 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
|
|||||||
value={issue.id}
|
value={issue.id}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
`flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-custom-text-200 ${
|
`flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-custom-text-200 ${
|
||||||
active || selected
|
active || selected ? "bg-custom-background-80 text-custom-text-100" : ""
|
||||||
? "bg-custom-background-80 text-custom-text-100"
|
|
||||||
: ""
|
|
||||||
} `
|
} `
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -154,11 +147,8 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="flex-shrink-0 text-xs text-custom-text-200">
|
<span className="flex-shrink-0 text-xs text-custom-text-200">
|
||||||
{
|
{issues?.find((i) => i.id === issue.id)?.project_detail?.identifier}-
|
||||||
issues?.find((i) => i.id === issue.id)?.project_detail
|
{issue.sequence_id}
|
||||||
?.identifier
|
|
||||||
}
|
|
||||||
-{issue.sequence_id}
|
|
||||||
</span>
|
</span>
|
||||||
<span className="text-custom-text-200">{issue.name}</span>
|
<span className="text-custom-text-200">{issue.name}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -171,10 +161,7 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
|
|||||||
<LayerDiagonalIcon height="56" width="56" />
|
<LayerDiagonalIcon height="56" width="56" />
|
||||||
<h3 className="text-sm text-custom-text-200">
|
<h3 className="text-sm text-custom-text-200">
|
||||||
No issues found. Create a new issue with{" "}
|
No issues found. Create a new issue with{" "}
|
||||||
<pre className="inline rounded bg-custom-background-80 px-2 py-1">
|
<pre className="inline rounded bg-custom-background-80 px-2 py-1">C</pre>.
|
||||||
C
|
|
||||||
</pre>
|
|
||||||
.
|
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -7,7 +7,7 @@ import { mutate } from "swr";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import IntegrationService from "services/integration";
|
import IntegrationService from "services/integration.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
@ -92,10 +92,7 @@ export const DeleteImportModal: React.FC<Props> = ({ isOpen, handleClose, data,
|
|||||||
<div className="flex flex-col gap-6 p-6">
|
<div className="flex flex-col gap-6 p-6">
|
||||||
<div className="flex w-full items-center justify-start gap-6">
|
<div className="flex w-full items-center justify-start gap-6">
|
||||||
<span className="place-items-center rounded-full bg-red-500/20 p-4">
|
<span className="place-items-center rounded-full bg-red-500/20 p-4">
|
||||||
<ExclamationTriangleIcon
|
<ExclamationTriangleIcon className="h-6 w-6 text-red-500" aria-hidden="true" />
|
||||||
className="h-6 w-6 text-red-500"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<span className="flex items-center justify-start">
|
<span className="flex items-center justify-start">
|
||||||
<h3 className="text-xl font-medium 2xl:text-2xl">Delete Project</h3>
|
<h3 className="text-xl font-medium 2xl:text-2xl">Delete Project</h3>
|
||||||
@ -104,17 +101,13 @@ export const DeleteImportModal: React.FC<Props> = ({ isOpen, handleClose, data,
|
|||||||
<span>
|
<span>
|
||||||
<p className="text-sm leading-7 text-custom-text-200">
|
<p className="text-sm leading-7 text-custom-text-200">
|
||||||
Are you sure you want to delete import from{" "}
|
Are you sure you want to delete import from{" "}
|
||||||
<span className="break-words font-semibold capitalize text-custom-text-100">
|
<span className="break-words font-semibold capitalize text-custom-text-100">{data?.service}</span>
|
||||||
{data?.service}
|
? All of the data related to the import will be permanently removed. This action cannot be undone.
|
||||||
</span>
|
|
||||||
? All of the data related to the import will be permanently removed. This
|
|
||||||
action cannot be undone.
|
|
||||||
</p>
|
</p>
|
||||||
</span>
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-custom-text-200">
|
<p className="text-sm text-custom-text-200">
|
||||||
To confirm, type{" "}
|
To confirm, type <span className="font-medium text-custom-text-100">delete import</span> below:
|
||||||
<span className="font-medium text-custom-text-100">delete import</span> below:
|
|
||||||
</p>
|
</p>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
@ -129,11 +122,7 @@ export const DeleteImportModal: React.FC<Props> = ({ isOpen, handleClose, data,
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
|
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
|
||||||
<DangerButton
|
<DangerButton onClick={handleDeletion} disabled={!confirmDeleteImport} loading={deleteLoading}>
|
||||||
onClick={handleDeletion}
|
|
||||||
disabled={!confirmDeleteImport}
|
|
||||||
loading={deleteLoading}
|
|
||||||
>
|
|
||||||
{deleteLoading ? "Deleting..." : "Delete Project"}
|
{deleteLoading ? "Deleting..." : "Delete Project"}
|
||||||
</DangerButton>
|
</DangerButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,7 +7,7 @@ import useSWR from "swr";
|
|||||||
// react-hook-form
|
// react-hook-form
|
||||||
import { UseFormSetValue } from "react-hook-form";
|
import { UseFormSetValue } from "react-hook-form";
|
||||||
// services
|
// services
|
||||||
import GithubIntegrationService from "services/integration/github.service";
|
import GithubIntegrationService from "services/github.service";
|
||||||
// ui
|
// ui
|
||||||
import { Loader, PrimaryButton, SecondaryButton } from "components/ui";
|
import { Loader, PrimaryButton, SecondaryButton } from "components/ui";
|
||||||
// types
|
// types
|
||||||
@ -22,19 +22,12 @@ type Props = {
|
|||||||
setValue: UseFormSetValue<TFormValues>;
|
setValue: UseFormSetValue<TFormValues>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GithubRepoDetails: FC<Props> = ({
|
export const GithubRepoDetails: FC<Props> = ({ selectedRepo, handleStepChange, setUsers, setValue }) => {
|
||||||
selectedRepo,
|
|
||||||
handleStepChange,
|
|
||||||
setUsers,
|
|
||||||
setValue,
|
|
||||||
}) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
const { data: repoInfo } = useSWR(
|
const { data: repoInfo } = useSWR(
|
||||||
workspaceSlug && selectedRepo
|
workspaceSlug && selectedRepo ? GITHUB_REPOSITORY_INFO(workspaceSlug as string, selectedRepo.name) : null,
|
||||||
? GITHUB_REPOSITORY_INFO(workspaceSlug as string, selectedRepo.name)
|
|
||||||
: null,
|
|
||||||
workspaceSlug && selectedRepo
|
workspaceSlug && selectedRepo
|
||||||
? () =>
|
? () =>
|
||||||
GithubIntegrationService.getGithubRepoInfo(workspaceSlug as string, {
|
GithubIntegrationService.getGithubRepoInfo(workspaceSlug as string, {
|
||||||
|
@ -9,8 +9,8 @@ import useSWR, { mutate } from "swr";
|
|||||||
// react-hook-form
|
// react-hook-form
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
// services
|
// services
|
||||||
import IntegrationService from "services/integration";
|
import IntegrationService from "services/integration.service";
|
||||||
import GithubIntegrationService from "services/integration/github.service";
|
import GithubIntegrationService from "services/github.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
@ -29,18 +29,9 @@ import GithubLogo from "public/services/github.png";
|
|||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IGithubRepoCollaborator, IGithubServiceImportFormData } from "types";
|
import { ICurrentUserResponse, IGithubRepoCollaborator, IGithubServiceImportFormData } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import { APP_INTEGRATIONS, IMPORTER_SERVICES_LIST, WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
|
||||||
APP_INTEGRATIONS,
|
|
||||||
IMPORTER_SERVICES_LIST,
|
|
||||||
WORKSPACE_INTEGRATIONS,
|
|
||||||
} from "constants/fetch-keys";
|
|
||||||
|
|
||||||
export type TIntegrationSteps =
|
export type TIntegrationSteps = "import-configure" | "import-data" | "repo-details" | "import-users" | "import-confirm";
|
||||||
| "import-configure"
|
|
||||||
| "import-data"
|
|
||||||
| "repo-details"
|
|
||||||
| "import-users"
|
|
||||||
| "import-confirm";
|
|
||||||
export interface IIntegrationData {
|
export interface IIntegrationData {
|
||||||
state: TIntegrationSteps;
|
state: TIntegrationSteps;
|
||||||
}
|
}
|
||||||
@ -108,21 +99,15 @@ export const GithubImporterRoot: React.FC<Props> = ({ user }) => {
|
|||||||
defaultValues: defaultFormValues,
|
defaultValues: defaultFormValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: appIntegrations } = useSWR(APP_INTEGRATIONS, () =>
|
const { data: appIntegrations } = useSWR(APP_INTEGRATIONS, () => IntegrationService.getAppIntegrationsList());
|
||||||
IntegrationService.getAppIntegrationsList()
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: workspaceIntegrations } = useSWR(
|
const { data: workspaceIntegrations } = useSWR(
|
||||||
workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null,
|
workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null,
|
||||||
workspaceSlug
|
workspaceSlug ? () => IntegrationService.getWorkspaceIntegrationsList(workspaceSlug as string) : null
|
||||||
? () => IntegrationService.getWorkspaceIntegrationsList(workspaceSlug as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const activeIntegrationState = () => {
|
const activeIntegrationState = () => {
|
||||||
const currentElementIndex = integrationWorkflowData.findIndex(
|
const currentElementIndex = integrationWorkflowData.findIndex((i) => i?.key === currentStep?.state);
|
||||||
(i) => i?.key === currentStep?.state
|
|
||||||
);
|
|
||||||
|
|
||||||
return currentElementIndex;
|
return currentElementIndex;
|
||||||
};
|
};
|
||||||
@ -133,14 +118,11 @@ export const GithubImporterRoot: React.FC<Props> = ({ user }) => {
|
|||||||
|
|
||||||
// current integration from all the integrations available
|
// current integration from all the integrations available
|
||||||
const integration =
|
const integration =
|
||||||
appIntegrations &&
|
appIntegrations && appIntegrations.length > 0 && appIntegrations.find((i) => i.provider === provider);
|
||||||
appIntegrations.length > 0 &&
|
|
||||||
appIntegrations.find((i) => i.provider === provider);
|
|
||||||
|
|
||||||
// current integration from workspace integrations
|
// current integration from workspace integrations
|
||||||
const workspaceIntegration =
|
const workspaceIntegration =
|
||||||
integration &&
|
integration && workspaceIntegrations?.find((i: any) => i.integration_detail.id === integration.id);
|
||||||
workspaceIntegrations?.find((i: any) => i.integration_detail.id === integration.id);
|
|
||||||
|
|
||||||
const createGithubImporterService = async (formData: TFormValues) => {
|
const createGithubImporterService = async (formData: TFormValues) => {
|
||||||
if (!formData.github || !formData.project) return;
|
if (!formData.github || !formData.project) return;
|
||||||
@ -214,9 +196,7 @@ export const GithubImporterRoot: React.FC<Props> = ({ user }) => {
|
|||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`border-b px-7 ${
|
className={`border-b px-7 ${
|
||||||
index <= activeIntegrationState() - 1
|
index <= activeIntegrationState() - 1 ? `border-custom-primary` : `border-custom-border-200`
|
||||||
? `border-custom-primary`
|
|
||||||
: `border-custom-border-200`
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{" "}
|
{" "}
|
||||||
|
@ -9,14 +9,9 @@ import useSWR, { mutate } from "swr";
|
|||||||
// hooks
|
// hooks
|
||||||
import useUserAuth from "hooks/use-user-auth";
|
import useUserAuth from "hooks/use-user-auth";
|
||||||
// services
|
// services
|
||||||
import IntegrationService from "services/integration";
|
import IntegrationService from "services/integration.service";
|
||||||
// components
|
// components
|
||||||
import {
|
import { DeleteImportModal, GithubImporterRoot, JiraImporterRoot, SingleImport } from "components/integration";
|
||||||
DeleteImportModal,
|
|
||||||
GithubImporterRoot,
|
|
||||||
JiraImporterRoot,
|
|
||||||
SingleImport,
|
|
||||||
} from "components/integration";
|
|
||||||
// ui
|
// ui
|
||||||
import { Loader, PrimaryButton } from "components/ui";
|
import { Loader, PrimaryButton } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
@ -85,18 +80,11 @@ const IntegrationGuide = () => {
|
|||||||
>
|
>
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="relative h-10 w-10 flex-shrink-0">
|
<div className="relative h-10 w-10 flex-shrink-0">
|
||||||
<Image
|
<Image src={service.logo} layout="fill" objectFit="cover" alt={`${service.title} Logo`} />
|
||||||
src={service.logo}
|
|
||||||
layout="fill"
|
|
||||||
objectFit="cover"
|
|
||||||
alt={`${service.title} Logo`}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="flex items-center gap-4 text-sm font-medium">{service.title}</h3>
|
<h3 className="flex items-center gap-4 text-sm font-medium">{service.title}</h3>
|
||||||
<p className="text-sm text-custom-text-200 tracking-tight">
|
<p className="text-sm text-custom-text-200 tracking-tight">{service.description}</p>
|
||||||
{service.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
@ -119,9 +107,7 @@ const IntegrationGuide = () => {
|
|||||||
className="flex flex-shrink-0 items-center gap-1 rounded bg-custom-background-80 py-1 px-1.5 text-xs outline-none"
|
className="flex flex-shrink-0 items-center gap-1 rounded bg-custom-background-80 py-1 px-1.5 text-xs outline-none"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
mutate(IMPORTER_SERVICES_LIST(workspaceSlug as string)).then(() =>
|
mutate(IMPORTER_SERVICES_LIST(workspaceSlug as string)).then(() => setRefreshing(false));
|
||||||
setRefreshing(false)
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ArrowPathIcon className={`h-3 w-3 ${refreshing ? "animate-spin" : ""}`} />{" "}
|
<ArrowPathIcon className={`h-3 w-3 ${refreshing ? "animate-spin" : ""}`} />{" "}
|
||||||
@ -145,9 +131,7 @@ const IntegrationGuide = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-custom-text-200 px-4 py-6">
|
<p className="text-sm text-custom-text-200 px-4 py-6">No previous imports available.</p>
|
||||||
No previous imports available.
|
|
||||||
</p>
|
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<Loader className="mt-6 grid grid-cols-1 gap-3">
|
<Loader className="mt-6 grid grid-cols-1 gap-3">
|
||||||
|
@ -10,7 +10,7 @@ import useSWR from "swr";
|
|||||||
import { useFormContext, Controller } from "react-hook-form";
|
import { useFormContext, Controller } from "react-hook-form";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import jiraImporterService from "services/integration/jira.service";
|
import jiraImporterService from "services/jira.service";
|
||||||
|
|
||||||
// fetch keys
|
// fetch keys
|
||||||
import { JIRA_IMPORTER_DETAIL } from "constants/fetch-keys";
|
import { JIRA_IMPORTER_DETAIL } from "constants/fetch-keys";
|
||||||
@ -157,9 +157,7 @@ export const JiraProjectDetail: React.FC<Props> = (props) => {
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="config.epics_to_modules"
|
name="config.epics_to_modules"
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => <ToggleSwitch onChange={onChange} value={value} />}
|
||||||
<ToggleSwitch onChange={onChange} value={value} />
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,7 +16,7 @@ import { ArrowLeftIcon, ListBulletIcon } from "@heroicons/react/24/outline";
|
|||||||
import { CogIcon, UsersIcon, CheckIcon } from "components/icons";
|
import { CogIcon, UsersIcon, CheckIcon } from "components/icons";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import jiraImporterService from "services/integration/jira.service";
|
import jiraImporterService from "services/jira.service";
|
||||||
|
|
||||||
// fetch keys
|
// fetch keys
|
||||||
import { IMPORTER_SERVICES_LIST } from "constants/fetch-keys";
|
import { IMPORTER_SERVICES_LIST } from "constants/fetch-keys";
|
||||||
@ -100,9 +100,7 @@ export const JiraImporterRoot: React.FC<Props> = ({ user }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const activeIntegrationState = () => {
|
const activeIntegrationState = () => {
|
||||||
const currentElementIndex = integrationWorkflowData.findIndex(
|
const currentElementIndex = integrationWorkflowData.findIndex((i) => i?.key === currentStep?.state);
|
||||||
(i) => i?.key === currentStep?.state
|
|
||||||
);
|
|
||||||
|
|
||||||
return currentElementIndex;
|
return currentElementIndex;
|
||||||
};
|
};
|
||||||
@ -155,9 +153,7 @@ export const JiraImporterRoot: React.FC<Props> = ({ user }) => {
|
|||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`border-b px-7 ${
|
className={`border-b px-7 ${
|
||||||
index <= activeIntegrationState() - 1
|
index <= activeIntegrationState() - 1 ? `border-custom-primary` : `border-custom-border-200`
|
||||||
? `border-custom-primary`
|
|
||||||
: `border-custom-border-200`
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{" "}
|
{" "}
|
||||||
@ -174,10 +170,7 @@ export const JiraImporterRoot: React.FC<Props> = ({ user }) => {
|
|||||||
<div className="h-full w-full overflow-y-auto">
|
<div className="h-full w-full overflow-y-auto">
|
||||||
{currentStep.state === "import-configure" && <JiraGetImportDetail />}
|
{currentStep.state === "import-configure" && <JiraGetImportDetail />}
|
||||||
{currentStep.state === "display-import-data" && (
|
{currentStep.state === "display-import-data" && (
|
||||||
<JiraProjectDetail
|
<JiraProjectDetail setDisableTopBarAfter={setDisableTopBarAfter} setCurrentStep={setCurrentStep} />
|
||||||
setDisableTopBarAfter={setDisableTopBarAfter}
|
|
||||||
setCurrentStep={setCurrentStep}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{currentStep?.state === "import-users" && <JiraImportUsers />}
|
{currentStep?.state === "import-users" && <JiraImportUsers />}
|
||||||
{currentStep?.state === "import-confirmation" && <JiraConfirmImport />}
|
{currentStep?.state === "import-confirmation" && <JiraConfirmImport />}
|
||||||
@ -199,15 +192,9 @@ export const JiraImporterRoot: React.FC<Props> = ({ user }) => {
|
|||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
)}
|
)}
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
disabled={
|
disabled={disableTopBarAfter === currentStep?.state || !isValid || methods.formState.isSubmitting}
|
||||||
disableTopBarAfter === currentStep?.state ||
|
|
||||||
!isValid ||
|
|
||||||
methods.formState.isSubmitting
|
|
||||||
}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const currentElementIndex = integrationWorkflowData.findIndex(
|
const currentElementIndex = integrationWorkflowData.findIndex((i) => i?.key === currentStep?.state);
|
||||||
(i) => i?.key === currentStep?.state
|
|
||||||
);
|
|
||||||
|
|
||||||
if (currentElementIndex === integrationWorkflowData.length - 1) {
|
if (currentElementIndex === integrationWorkflowData.length - 1) {
|
||||||
methods.handleSubmit(onSubmit)();
|
methods.handleSubmit(onSubmit)();
|
||||||
|
@ -6,7 +6,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import IntegrationService from "services/integration";
|
import IntegrationService from "services/integration.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useIntegrationPopup from "hooks/use-integration-popup";
|
import useIntegrationPopup from "hooks/use-integration-popup";
|
||||||
@ -50,25 +50,17 @@ export const SingleIntegrationCard: React.FC<Props> = ({ integration }) => {
|
|||||||
|
|
||||||
const { data: workspaceIntegrations } = useSWR(
|
const { data: workspaceIntegrations } = useSWR(
|
||||||
workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null,
|
workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null,
|
||||||
() =>
|
() => (workspaceSlug ? IntegrationService.getWorkspaceIntegrationsList(workspaceSlug as string) : null)
|
||||||
workspaceSlug
|
|
||||||
? IntegrationService.getWorkspaceIntegrationsList(workspaceSlug as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleRemoveIntegration = async () => {
|
const handleRemoveIntegration = async () => {
|
||||||
if (!workspaceSlug || !integration || !workspaceIntegrations) return;
|
if (!workspaceSlug || !integration || !workspaceIntegrations) return;
|
||||||
|
|
||||||
const workspaceIntegrationId = workspaceIntegrations?.find(
|
const workspaceIntegrationId = workspaceIntegrations?.find((i) => i.integration === integration.id)?.id;
|
||||||
(i) => i.integration === integration.id
|
|
||||||
)?.id;
|
|
||||||
|
|
||||||
setDeletingIntegration(true);
|
setDeletingIntegration(true);
|
||||||
|
|
||||||
await IntegrationService.deleteWorkspaceIntegration(
|
await IntegrationService.deleteWorkspaceIntegration(workspaceSlug as string, workspaceIntegrationId ?? "")
|
||||||
workspaceSlug as string,
|
|
||||||
workspaceIntegrationId ?? ""
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutate<IWorkspaceIntegration[]>(
|
mutate<IWorkspaceIntegration[]>(
|
||||||
WORKSPACE_INTEGRATIONS(workspaceSlug as string),
|
WORKSPACE_INTEGRATIONS(workspaceSlug as string),
|
||||||
@ -94,18 +86,13 @@ export const SingleIntegrationCard: React.FC<Props> = ({ integration }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const isInstalled = workspaceIntegrations?.find(
|
const isInstalled = workspaceIntegrations?.find((i: any) => i.integration_detail.id === integration.id);
|
||||||
(i: any) => i.integration_detail.id === integration.id
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between gap-2 border-b border-custom-border-200 bg-custom-background-100 px-4 py-6">
|
<div className="flex items-center justify-between gap-2 border-b border-custom-border-200 bg-custom-background-100 px-4 py-6">
|
||||||
<div className="flex items-start gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="h-10 w-10 flex-shrink-0">
|
<div className="h-10 w-10 flex-shrink-0">
|
||||||
<Image
|
<Image src={integrationDetails[integration.provider].logo} alt={`${integration.title} Logo`} />
|
||||||
src={integrationDetails[integration.provider].logo}
|
|
||||||
alt={`${integration.title} Logo`}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="flex items-center gap-2 text-sm font-medium">
|
<h3 className="flex items-center gap-2 text-sm font-medium">
|
||||||
|
@ -4,7 +4,7 @@ import { useRouter } from "next/router";
|
|||||||
|
|
||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
// services
|
// services
|
||||||
import appinstallationsService from "services/app-installations.service";
|
import appinstallationsService from "services/app_installation.service";
|
||||||
// ui
|
// ui
|
||||||
import { Loader } from "components/ui";
|
import { Loader } from "components/ui";
|
||||||
// hooks
|
// hooks
|
||||||
@ -20,8 +20,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SelectChannel: React.FC<Props> = ({ integration }) => {
|
export const SelectChannel: React.FC<Props> = ({ integration }) => {
|
||||||
const [slackChannelAvailabilityToggle, setSlackChannelAvailabilityToggle] =
|
const [slackChannelAvailabilityToggle, setSlackChannelAvailabilityToggle] = useState<boolean>(false);
|
||||||
useState<boolean>(false);
|
|
||||||
const [slackChannel, setSlackChannel] = useState<ISlackIntegration | null>(null);
|
const [slackChannel, setSlackChannel] = useState<ISlackIntegration | null>(null);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -65,12 +64,7 @@ export const SelectChannel: React.FC<Props> = ({ integration }) => {
|
|||||||
setSlackChannel(null);
|
setSlackChannel(null);
|
||||||
});
|
});
|
||||||
appinstallationsService
|
appinstallationsService
|
||||||
.removeSlackChannel(
|
.removeSlackChannel(workspaceSlug as string, projectId as string, integration.id as string, slackChannel?.id)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
integration.id as string,
|
|
||||||
slackChannel?.id
|
|
||||||
)
|
|
||||||
.catch((err) => console.log(err));
|
.catch((err) => console.log(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import { observer } from "mobx-react-lite";
|
|||||||
import { RootStore } from "store/root";
|
import { RootStore } from "store/root";
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
// types and default data
|
// types and default data
|
||||||
import { TIssueLayouts } from "store/issue-filters";
|
import { TIssueLayouts } from "store/issue_filters.legacy";
|
||||||
import { issueFilterVisibilityData } from "store/helpers/issue-data";
|
import { issueFilterVisibilityData } from "store/helpers/issue-data";
|
||||||
|
|
||||||
export const LayoutSelection = observer(() => {
|
export const LayoutSelection = observer(() => {
|
||||||
|
@ -7,7 +7,7 @@ import { mutate } from "swr";
|
|||||||
// react-dropzone
|
// react-dropzone
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// types
|
// types
|
||||||
@ -44,12 +44,7 @@ export const IssueAttachmentUpload: React.FC<Props> = ({ disabled = false }) =>
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
issuesService
|
issuesService
|
||||||
.uploadIssueAttachment(
|
.uploadIssueAttachment(workspaceSlug as string, projectId as string, issueId as string, formData)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
issueId as string,
|
|
||||||
formData
|
|
||||||
)
|
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
mutate<IIssueAttachment[]>(
|
mutate<IIssueAttachment[]>(
|
||||||
ISSUE_ATTACHMENTS(issueId as string),
|
ISSUE_ATTACHMENTS(issueId as string),
|
||||||
@ -83,9 +78,7 @@ export const IssueAttachmentUpload: React.FC<Props> = ({ disabled = false }) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
const fileError =
|
const fileError =
|
||||||
fileRejections.length > 0
|
fileRejections.length > 0 ? `Invalid file type or size (max ${maxFileSize / 1024 / 1024} MB)` : null;
|
||||||
? `Invalid file type or size (max ${maxFileSize / 1024 / 1024} MB)`
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -12,7 +12,7 @@ import { DeleteAttachmentModal } from "./delete-attachment-modal";
|
|||||||
import { XMarkIcon } from "@heroicons/react/24/outline";
|
import { XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
import { ExclamationIcon, getFileIcon } from "components/icons";
|
import { ExclamationIcon, getFileIcon } from "components/icons";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
// fetch-key
|
// fetch-key
|
||||||
import { ISSUE_ATTACHMENTS, PROJECT_MEMBERS } from "constants/fetch-keys";
|
import { ISSUE_ATTACHMENTS, PROJECT_MEMBERS } from "constants/fetch-keys";
|
||||||
@ -33,12 +33,7 @@ export const IssueAttachments = () => {
|
|||||||
const { data: attachments } = useSWR<IIssueAttachment[]>(
|
const { data: attachments } = useSWR<IIssueAttachment[]>(
|
||||||
workspaceSlug && projectId && issueId ? ISSUE_ATTACHMENTS(issueId as string) : null,
|
workspaceSlug && projectId && issueId ? ISSUE_ATTACHMENTS(issueId as string) : null,
|
||||||
workspaceSlug && projectId && issueId
|
workspaceSlug && projectId && issueId
|
||||||
? () =>
|
? () => issuesService.getIssueAttachment(workspaceSlug as string, projectId as string, issueId as string)
|
||||||
issuesService.getIssueAttachment(
|
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
issueId as string
|
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -70,14 +65,11 @@ export const IssueAttachments = () => {
|
|||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Tooltip tooltipContent={getFileName(file.attributes.name)}>
|
<Tooltip tooltipContent={getFileName(file.attributes.name)}>
|
||||||
<span className="text-sm">
|
<span className="text-sm">{truncateText(`${getFileName(file.attributes.name)}`, 10)}</span>
|
||||||
{truncateText(`${getFileName(file.attributes.name)}`, 10)}
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
tooltipContent={`${
|
tooltipContent={`${
|
||||||
people?.find((person) => person.member.id === file.updated_by)?.member
|
people?.find((person) => person.member.id === file.updated_by)?.member.display_name ?? ""
|
||||||
.display_name ?? ""
|
|
||||||
} uploaded on ${renderLongDateFormat(file.updated_at)}`}
|
} uploaded on ${renderLongDateFormat(file.updated_at)}`}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
|
@ -7,7 +7,7 @@ import { mutate } from "swr";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
@ -47,12 +47,7 @@ export const DeleteAttachmentModal: React.FC<Props> = ({ isOpen, setIsOpen, data
|
|||||||
);
|
);
|
||||||
|
|
||||||
await issuesService
|
await issuesService
|
||||||
.deleteIssueAttachment(
|
.deleteIssueAttachment(workspaceSlug as string, projectId as string, issueId as string, assetId as string)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
issueId as string,
|
|
||||||
assetId as string
|
|
||||||
)
|
|
||||||
.then(() => mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)))
|
.then(() => mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)))
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
@ -94,24 +89,17 @@ export const DeleteAttachmentModal: React.FC<Props> = ({ isOpen, setIsOpen, data
|
|||||||
<div className="bg-custom-background-80 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
<div className="bg-custom-background-80 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
<div className="sm:flex sm:items-start">
|
<div className="sm:flex sm:items-start">
|
||||||
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
<ExclamationTriangleIcon
|
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
|
||||||
className="h-6 w-6 text-red-600"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
<Dialog.Title
|
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
|
||||||
as="h3"
|
|
||||||
className="text-lg font-medium leading-6 text-custom-text-100"
|
|
||||||
>
|
|
||||||
Delete Attachment
|
Delete Attachment
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-sm text-custom-text-200">
|
<p className="text-sm text-custom-text-200">
|
||||||
Are you sure you want to delete attachment-{" "}
|
Are you sure you want to delete attachment-{" "}
|
||||||
<span className="font-bold">{getFileName(data.attributes.name)}</span>?
|
<span className="font-bold">{getFileName(data.attributes.name)}</span>? This attachment will
|
||||||
This attachment will be permanently removed. This action cannot be
|
be permanently removed. This action cannot be undone.
|
||||||
undone.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,7 +7,7 @@ import { mutate } from "swr";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issueServices from "services/issues.service";
|
import issueServices from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
@ -109,10 +109,7 @@ export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
|
|||||||
<div className="flex flex-col gap-6 p-6">
|
<div className="flex flex-col gap-6 p-6">
|
||||||
<div className="flex w-full items-center justify-start gap-6">
|
<div className="flex w-full items-center justify-start gap-6">
|
||||||
<span className="place-items-center rounded-full bg-red-500/20 p-4">
|
<span className="place-items-center rounded-full bg-red-500/20 p-4">
|
||||||
<ExclamationTriangleIcon
|
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
|
||||||
className="h-6 w-6 text-red-600"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<span className="flex items-center justify-start">
|
<span className="flex items-center justify-start">
|
||||||
<h3 className="text-xl font-medium 2xl:text-2xl">Delete Draft Issue</h3>
|
<h3 className="text-xl font-medium 2xl:text-2xl">Delete Draft Issue</h3>
|
||||||
@ -124,8 +121,8 @@ export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
|
|||||||
<span className="break-words font-medium text-custom-text-100">
|
<span className="break-words font-medium text-custom-text-100">
|
||||||
{data?.project_detail.identifier}-{data?.sequence_id}
|
{data?.project_detail.identifier}-{data?.sequence_id}
|
||||||
</span>
|
</span>
|
||||||
{""}? All of the data related to the draft issue will be permanently removed.
|
{""}? All of the data related to the draft issue will be permanently removed. This action cannot
|
||||||
This action cannot be undone.
|
be undone.
|
||||||
</p>
|
</p>
|
||||||
</span>
|
</span>
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
|
@ -7,7 +7,7 @@ import { mutate } from "swr";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issueServices from "services/issues.service";
|
import issueServices from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
import useCalendarIssuesView from "hooks/use-calendar-issues-view";
|
||||||
@ -37,13 +37,7 @@ type Props = {
|
|||||||
onSubmit?: () => Promise<void>;
|
onSubmit?: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DeleteIssueModal: React.FC<Props> = ({
|
export const DeleteIssueModal: React.FC<Props> = ({ isOpen, handleClose, data, user, onSubmit }) => {
|
||||||
isOpen,
|
|
||||||
handleClose,
|
|
||||||
data,
|
|
||||||
user,
|
|
||||||
onSubmit,
|
|
||||||
}) => {
|
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -82,11 +76,7 @@ export const DeleteIssueModal: React.FC<Props> = ({
|
|||||||
? VIEW_ISSUES(viewId.toString(), calendarParams)
|
? VIEW_ISSUES(viewId.toString(), calendarParams)
|
||||||
: PROJECT_ISSUES_LIST_WITH_PARAMS(data.project, calendarParams);
|
: PROJECT_ISSUES_LIST_WITH_PARAMS(data.project, calendarParams);
|
||||||
|
|
||||||
mutate<IIssue[]>(
|
mutate<IIssue[]>(calendarFetchKey, (prevData) => (prevData ?? []).filter((p) => p.id !== data.id), false);
|
||||||
calendarFetchKey,
|
|
||||||
(prevData) => (prevData ?? []).filter((p) => p.id !== data.id),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
} else if (displayFilters.layout === "spreadsheet") {
|
} else if (displayFilters.layout === "spreadsheet") {
|
||||||
const spreadsheetFetchKey = cycleId
|
const spreadsheetFetchKey = cycleId
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), spreadsheetParams)
|
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), spreadsheetParams)
|
||||||
@ -163,8 +153,7 @@ export const DeleteIssueModal: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleIssueDelete = () =>
|
const handleIssueDelete = () => (isArchivedIssues ? handleArchivedIssueDeletion() : handleDeletion());
|
||||||
isArchivedIssues ? handleArchivedIssueDeletion() : handleDeletion();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||||
@ -196,10 +185,7 @@ export const DeleteIssueModal: React.FC<Props> = ({
|
|||||||
<div className="flex flex-col gap-6 p-6">
|
<div className="flex flex-col gap-6 p-6">
|
||||||
<div className="flex w-full items-center justify-start gap-6">
|
<div className="flex w-full items-center justify-start gap-6">
|
||||||
<span className="place-items-center rounded-full bg-red-500/20 p-4">
|
<span className="place-items-center rounded-full bg-red-500/20 p-4">
|
||||||
<ExclamationTriangleIcon
|
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
|
||||||
className="h-6 w-6 text-red-600"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<span className="flex items-center justify-start">
|
<span className="flex items-center justify-start">
|
||||||
<h3 className="text-xl font-medium 2xl:text-2xl">Delete Issue</h3>
|
<h3 className="text-xl font-medium 2xl:text-2xl">Delete Issue</h3>
|
||||||
@ -211,8 +197,8 @@ export const DeleteIssueModal: React.FC<Props> = ({
|
|||||||
<span className="break-words font-medium text-custom-text-100">
|
<span className="break-words font-medium text-custom-text-100">
|
||||||
{data?.project_detail.identifier}-{data?.sequence_id}
|
{data?.project_detail.identifier}-{data?.sequence_id}
|
||||||
</span>
|
</span>
|
||||||
{""}? All of the data related to the issue will be permanently removed. This
|
{""}? All of the data related to the issue will be permanently removed. This action cannot be
|
||||||
action cannot be undone.
|
undone.
|
||||||
</p>
|
</p>
|
||||||
</span>
|
</span>
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
|
@ -7,7 +7,7 @@ import { mutate } from "swr";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
@ -114,8 +114,7 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prePopulateData && prePopulateData.project)
|
if (prePopulateData && prePopulateData.project) return setActiveProject(prePopulateData.project);
|
||||||
return setActiveProject(prePopulateData.project);
|
|
||||||
|
|
||||||
// if data is not present, set active project to the project
|
// if data is not present, set active project to the project
|
||||||
// in the url. This has the least priority.
|
// in the url. This has the least priority.
|
||||||
|
@ -4,7 +4,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUserAuth from "hooks/use-user-auth";
|
import useUserAuth from "hooks/use-user-auth";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
@ -37,11 +37,7 @@ type Props = {
|
|||||||
uneditable?: boolean;
|
uneditable?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueMainContent: React.FC<Props> = ({
|
export const IssueMainContent: React.FC<Props> = ({ issueDetails, submitChanges, uneditable = false }) => {
|
||||||
issueDetails,
|
|
||||||
submitChanges,
|
|
||||||
uneditable = false,
|
|
||||||
}) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, issueId, archivedIssueId } = router.query;
|
const { workspaceSlug, projectId, issueId, archivedIssueId } = router.query;
|
||||||
|
|
||||||
@ -55,12 +51,7 @@ export const IssueMainContent: React.FC<Props> = ({
|
|||||||
const { data: siblingIssues } = useSWR(
|
const { data: siblingIssues } = useSWR(
|
||||||
workspaceSlug && projectId && issueDetails?.parent ? SUB_ISSUES(issueDetails.parent) : null,
|
workspaceSlug && projectId && issueDetails?.parent ? SUB_ISSUES(issueDetails.parent) : null,
|
||||||
workspaceSlug && projectId && issueDetails?.parent
|
workspaceSlug && projectId && issueDetails?.parent
|
||||||
? () =>
|
? () => issuesService.subIssues(workspaceSlug as string, projectId as string, issueDetails.parent ?? "")
|
||||||
issuesService.subIssues(
|
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
issueDetails.parent ?? ""
|
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
const siblingIssuesList = siblingIssues?.sub_issues.filter((i) => i.id !== issueDetails.id);
|
const siblingIssuesList = siblingIssues?.sub_issues.filter((i) => i.id !== issueDetails.id);
|
||||||
@ -68,12 +59,7 @@ export const IssueMainContent: React.FC<Props> = ({
|
|||||||
const { data: issueActivity, mutate: mutateIssueActivity } = useSWR(
|
const { data: issueActivity, mutate: mutateIssueActivity } = useSWR(
|
||||||
workspaceSlug && projectId && issueId ? PROJECT_ISSUES_ACTIVITY(issueId.toString()) : null,
|
workspaceSlug && projectId && issueId ? PROJECT_ISSUES_ACTIVITY(issueId.toString()) : null,
|
||||||
workspaceSlug && projectId && issueId
|
workspaceSlug && projectId && issueId
|
||||||
? () =>
|
? () => issuesService.getIssueActivities(workspaceSlug.toString(), projectId.toString(), issueId.toString())
|
||||||
issuesService.getIssueActivities(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
issueId.toString()
|
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -81,14 +67,7 @@ export const IssueMainContent: React.FC<Props> = ({
|
|||||||
if (!workspaceSlug || !projectId || !issueId) return;
|
if (!workspaceSlug || !projectId || !issueId) return;
|
||||||
|
|
||||||
await issuesService
|
await issuesService
|
||||||
.patchIssueComment(
|
.patchIssueComment(workspaceSlug as string, projectId as string, issueId as string, commentId, data, user)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
issueId as string,
|
|
||||||
commentId,
|
|
||||||
data,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => mutateIssueActivity());
|
.then(() => mutateIssueActivity());
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -98,13 +77,7 @@ export const IssueMainContent: React.FC<Props> = ({
|
|||||||
mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false);
|
mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false);
|
||||||
|
|
||||||
await issuesService
|
await issuesService
|
||||||
.deleteIssueComment(
|
.deleteIssueComment(workspaceSlug as string, projectId as string, issueId as string, commentId, user)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
issueId as string,
|
|
||||||
commentId,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => mutateIssueActivity());
|
.then(() => mutateIssueActivity());
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -112,13 +85,7 @@ export const IssueMainContent: React.FC<Props> = ({
|
|||||||
if (!workspaceSlug || !issueDetails) return;
|
if (!workspaceSlug || !issueDetails) return;
|
||||||
|
|
||||||
await issuesService
|
await issuesService
|
||||||
.createIssueComment(
|
.createIssueComment(workspaceSlug.toString(), issueDetails.project, issueDetails.id, formData, user)
|
||||||
workspaceSlug.toString(),
|
|
||||||
issueDetails.project,
|
|
||||||
issueDetails.id,
|
|
||||||
formData,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutate(PROJECT_ISSUES_ACTIVITY(issueDetails.id));
|
mutate(PROJECT_ISSUES_ACTIVITY(issueDetails.id));
|
||||||
})
|
})
|
||||||
@ -148,8 +115,7 @@ export const IssueMainContent: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span className="flex-shrink-0 text-custom-text-200">
|
<span className="flex-shrink-0 text-custom-text-200">
|
||||||
{issueDetails.parent_detail?.project_detail.identifier}-
|
{issueDetails.parent_detail?.project_detail.identifier}-{issueDetails.parent_detail?.sequence_id}
|
||||||
{issueDetails.parent_detail?.sequence_id}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="truncate text-custom-text-100">
|
<span className="truncate text-custom-text-100">
|
||||||
@ -169,9 +135,7 @@ export const IssueMainContent: React.FC<Props> = ({
|
|||||||
<CustomMenu.MenuItem
|
<CustomMenu.MenuItem
|
||||||
key={issue.id}
|
key={issue.id}
|
||||||
renderAs="a"
|
renderAs="a"
|
||||||
href={`/${workspaceSlug}/projects/${projectId as string}/issues/${
|
href={`/${workspaceSlug}/projects/${projectId as string}/issues/${issue.id}`}
|
||||||
issue.id
|
|
||||||
}`}
|
|
||||||
className="flex items-center gap-2 py-2"
|
className="flex items-center gap-2 py-2"
|
||||||
>
|
>
|
||||||
<LayerDiagonalIcon className="h-4 w-4" />
|
<LayerDiagonalIcon className="h-4 w-4" />
|
||||||
|
@ -8,7 +8,7 @@ import { mutate } from "swr";
|
|||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
import inboxServices from "services/inbox.service";
|
import inboxServices from "services/inbox.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
@ -93,8 +93,10 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
|
|
||||||
const { groupedIssues, mutateMyIssues } = useMyIssues(workspaceSlug?.toString());
|
const { groupedIssues, mutateMyIssues } = useMyIssues(workspaceSlug?.toString());
|
||||||
|
|
||||||
const { setValue: setValueInLocalStorage, clearValue: clearLocalStorageValue } =
|
const { setValue: setValueInLocalStorage, clearValue: clearLocalStorageValue } = useLocalStorage<any>(
|
||||||
useLocalStorage<any>("draftedIssue", {});
|
"draftedIssue",
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
@ -201,13 +203,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
await inboxServices
|
await inboxServices
|
||||||
.createInboxIssue(
|
.createInboxIssue(workspaceSlug.toString(), activeProject.toString(), inboxId.toString(), payload, user)
|
||||||
workspaceSlug.toString(),
|
|
||||||
activeProject.toString(),
|
|
||||||
inboxId.toString(),
|
|
||||||
payload,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
@ -264,8 +260,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = ({
|
|||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params));
|
||||||
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle);
|
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle);
|
||||||
if (payload.module && payload.module !== "")
|
if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module);
|
||||||
await addIssueToModule(res.id, payload.module);
|
|
||||||
|
|
||||||
if (displayFilters.layout === "calendar") mutate(calendarFetchKey);
|
if (displayFilters.layout === "calendar") mutate(calendarFetchKey);
|
||||||
if (displayFilters.layout === "gantt_chart")
|
if (displayFilters.layout === "gantt_chart")
|
||||||
|
@ -5,7 +5,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// components
|
// components
|
||||||
import { DateFilterModal } from "components/core";
|
import { DateFilterModal } from "components/core";
|
||||||
// ui
|
// ui
|
||||||
@ -29,12 +29,7 @@ type Props = {
|
|||||||
height?: "sm" | "md" | "rg" | "lg";
|
height?: "sm" | "md" | "rg" | "lg";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MyIssuesSelectFilters: React.FC<Props> = ({
|
export const MyIssuesSelectFilters: React.FC<Props> = ({ filters, onSelect, direction = "right", height = "md" }) => {
|
||||||
filters,
|
|
||||||
onSelect,
|
|
||||||
direction = "right",
|
|
||||||
height = "md",
|
|
||||||
}) => {
|
|
||||||
const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false);
|
const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false);
|
||||||
const [dateFilterType, setDateFilterType] = useState<{
|
const [dateFilterType, setDateFilterType] = useState<{
|
||||||
title: string;
|
title: string;
|
||||||
@ -50,9 +45,7 @@ export const MyIssuesSelectFilters: React.FC<Props> = ({
|
|||||||
|
|
||||||
const { data: labels } = useSWR(
|
const { data: labels } = useSWR(
|
||||||
workspaceSlug && fetchLabels ? WORKSPACE_LABELS(workspaceSlug.toString()) : null,
|
workspaceSlug && fetchLabels ? WORKSPACE_LABELS(workspaceSlug.toString()) : null,
|
||||||
workspaceSlug && fetchLabels
|
workspaceSlug && fetchLabels ? () => issuesService.getWorkspaceLabels(workspaceSlug.toString()) : null
|
||||||
? () => issuesService.getWorkspaceLabels(workspaceSlug.toString())
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -7,7 +7,7 @@ import useSWR, { mutate } from "swr";
|
|||||||
// react-beautiful-dnd
|
// react-beautiful-dnd
|
||||||
import { DropResult } from "react-beautiful-dnd";
|
import { DropResult } from "react-beautiful-dnd";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useMyIssues from "hooks/my-issues/use-my-issues";
|
import useMyIssues from "hooks/my-issues/use-my-issues";
|
||||||
import useMyIssuesFilters from "hooks/my-issues/use-my-issues-filter";
|
import useMyIssuesFilters from "hooks/my-issues/use-my-issues-filter";
|
||||||
@ -28,10 +28,7 @@ type Props = {
|
|||||||
disableUserActions?: false;
|
disableUserActions?: false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MyIssuesView: React.FC<Props> = ({
|
export const MyIssuesView: React.FC<Props> = ({ openIssuesListModal, disableUserActions = false }) => {
|
||||||
openIssuesListModal,
|
|
||||||
disableUserActions = false,
|
|
||||||
}) => {
|
|
||||||
// create issue modal
|
// create issue modal
|
||||||
const [createIssueModal, setCreateIssueModal] = useState(false);
|
const [createIssueModal, setCreateIssueModal] = useState(false);
|
||||||
const [preloadedData, setPreloadedData] = useState<
|
const [preloadedData, setPreloadedData] = useState<
|
||||||
@ -40,9 +37,7 @@ export const MyIssuesView: React.FC<Props> = ({
|
|||||||
|
|
||||||
// update issue modal
|
// update issue modal
|
||||||
const [editIssueModal, setEditIssueModal] = useState(false);
|
const [editIssueModal, setEditIssueModal] = useState(false);
|
||||||
const [issueToEdit, setIssueToEdit] = useState<
|
const [issueToEdit, setIssueToEdit] = useState<(IIssue & { actionType: "edit" | "delete" }) | undefined>(undefined);
|
||||||
(IIssue & { actionType: "edit" | "delete" }) | undefined
|
|
||||||
>(undefined);
|
|
||||||
|
|
||||||
// delete issue modal
|
// delete issue modal
|
||||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||||
@ -57,14 +52,10 @@ export const MyIssuesView: React.FC<Props> = ({
|
|||||||
const { user } = useUserAuth();
|
const { user } = useUserAuth();
|
||||||
|
|
||||||
const { groupedIssues, mutateMyIssues, isEmpty, params } = useMyIssues(workspaceSlug?.toString());
|
const { groupedIssues, mutateMyIssues, isEmpty, params } = useMyIssues(workspaceSlug?.toString());
|
||||||
const { filters, setFilters, displayFilters, properties } = useMyIssuesFilters(
|
const { filters, setFilters, displayFilters, properties } = useMyIssuesFilters(workspaceSlug?.toString());
|
||||||
workspaceSlug?.toString()
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: labels } = useSWR(
|
const { data: labels } = useSWR(
|
||||||
workspaceSlug && (filters?.labels ?? []).length > 0
|
workspaceSlug && (filters?.labels ?? []).length > 0 ? WORKSPACE_LABELS(workspaceSlug.toString()) : null,
|
||||||
? WORKSPACE_LABELS(workspaceSlug.toString())
|
|
||||||
: null,
|
|
||||||
workspaceSlug && (filters?.labels ?? []).length > 0
|
workspaceSlug && (filters?.labels ?? []).length > 0
|
||||||
? () => issuesService.getWorkspaceLabels(workspaceSlug.toString())
|
? () => issuesService.getWorkspaceLabels(workspaceSlug.toString())
|
||||||
: null
|
: null
|
||||||
@ -82,13 +73,7 @@ export const MyIssuesView: React.FC<Props> = ({
|
|||||||
async (result: DropResult) => {
|
async (result: DropResult) => {
|
||||||
setTrashBox(false);
|
setTrashBox(false);
|
||||||
|
|
||||||
if (
|
if (!result.destination || !workspaceSlug || !groupedIssues || displayFilters?.group_by !== "priority") return;
|
||||||
!result.destination ||
|
|
||||||
!workspaceSlug ||
|
|
||||||
!groupedIssues ||
|
|
||||||
displayFilters?.group_by !== "priority"
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const { source, destination } = result;
|
const { source, destination } = result;
|
||||||
|
|
||||||
@ -120,14 +105,8 @@ export const MyIssuesView: React.FC<Props> = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...prevData,
|
...prevData,
|
||||||
[sourceGroup]: orderArrayBy(
|
[sourceGroup]: orderArrayBy(sourceGroupArray, displayFilters.order_by ?? "-created_at"),
|
||||||
sourceGroupArray,
|
[destinationGroup]: orderArrayBy(destinationGroupArray, displayFilters.order_by ?? "-created_at"),
|
||||||
displayFilters.order_by ?? "-created_at"
|
|
||||||
),
|
|
||||||
[destinationGroup]: orderArrayBy(
|
|
||||||
destinationGroupArray,
|
|
||||||
displayFilters.order_by ?? "-created_at"
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
@ -219,15 +198,11 @@ export const MyIssuesView: React.FC<Props> = ({
|
|||||||
(key) => filtersToDisplay[key as keyof IIssueFilterOptions] === null
|
(key) => filtersToDisplay[key as keyof IIssueFilterOptions] === null
|
||||||
);
|
);
|
||||||
const areFiltersApplied =
|
const areFiltersApplied =
|
||||||
Object.keys(filtersToDisplay).length > 0 &&
|
Object.keys(filtersToDisplay).length > 0 && nullFilters.length !== Object.keys(filtersToDisplay).length;
|
||||||
nullFilters.length !== Object.keys(filtersToDisplay).length;
|
|
||||||
|
|
||||||
const isSubscribedIssuesRoute = router.pathname.includes("subscribed");
|
const isSubscribedIssuesRoute = router.pathname.includes("subscribed");
|
||||||
const isMySubscribedIssues =
|
const isMySubscribedIssues =
|
||||||
(filters.subscriber &&
|
(filters.subscriber && filters.subscriber.length > 0 && router.pathname.includes("my-issues")) ?? false;
|
||||||
filters.subscriber.length > 0 &&
|
|
||||||
router.pathname.includes("my-issues")) ??
|
|
||||||
false;
|
|
||||||
|
|
||||||
const disableAddIssueOption = isSubscribedIssuesRoute || isMySubscribedIssues;
|
const disableAddIssueOption = isSubscribedIssuesRoute || isMySubscribedIssues;
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
@ -78,10 +78,7 @@ export const PeekOverviewIssueActivity: React.FC<Props> = ({ workspaceSlug, issu
|
|||||||
showAccessSpecifier={projectDetails && projectDetails.is_deployed}
|
showAccessSpecifier={projectDetails && projectDetails.is_deployed}
|
||||||
/>
|
/>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<AddComment
|
<AddComment onSubmit={handleAddComment} showAccessSpecifier={projectDetails && projectDetails.is_deployed} />
|
||||||
onSubmit={handleAddComment}
|
|
||||||
showAccessSpecifier={projectDetails && projectDetails.is_deployed}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,17 +7,11 @@ import useSWR from "swr";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Combobox, Transition } from "@headlessui/react";
|
import { Combobox, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesServices from "services/issues.service";
|
import issuesServices from "services/issue.service";
|
||||||
// ui
|
// ui
|
||||||
import { IssueLabelsList } from "components/ui";
|
import { IssueLabelsList } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import {
|
import { CheckIcon, MagnifyingGlassIcon, PlusIcon, RectangleGroupIcon, TagIcon } from "@heroicons/react/24/outline";
|
||||||
CheckIcon,
|
|
||||||
MagnifyingGlassIcon,
|
|
||||||
PlusIcon,
|
|
||||||
RectangleGroupIcon,
|
|
||||||
TagIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
// types
|
// types
|
||||||
import type { IIssueLabels } from "types";
|
import type { IIssueLabels } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
@ -39,24 +33,14 @@ export const IssueLabelSelect: React.FC<Props> = ({ setIsOpen, value, onChange,
|
|||||||
|
|
||||||
const { data: issueLabels } = useSWR<IIssueLabels[]>(
|
const { data: issueLabels } = useSWR<IIssueLabels[]>(
|
||||||
projectId ? PROJECT_ISSUE_LABELS(projectId) : null,
|
projectId ? PROJECT_ISSUE_LABELS(projectId) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? () => issuesServices.getIssueLabels(workspaceSlug as string, projectId) : null
|
||||||
? () => issuesServices.getIssueLabels(workspaceSlug as string, projectId)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredOptions =
|
const filteredOptions =
|
||||||
query === ""
|
query === "" ? issueLabels : issueLabels?.filter((l) => l.name.toLowerCase().includes(query.toLowerCase()));
|
||||||
? issueLabels
|
|
||||||
: issueLabels?.filter((l) => l.name.toLowerCase().includes(query.toLowerCase()));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Combobox
|
<Combobox as="div" value={value} onChange={(val) => onChange(val)} className="relative flex-shrink-0" multiple>
|
||||||
as="div"
|
|
||||||
value={value}
|
|
||||||
onChange={(val) => onChange(val)}
|
|
||||||
className="relative flex-shrink-0"
|
|
||||||
multiple
|
|
||||||
>
|
|
||||||
{({ open }: any) => (
|
{({ open }: any) => (
|
||||||
<>
|
<>
|
||||||
<Combobox.Button className="flex items-center gap-2 cursor-pointer text-xs text-custom-text-200">
|
<Combobox.Button className="flex items-center gap-2 cursor-pointer text-xs text-custom-text-200">
|
||||||
@ -129,11 +113,7 @@ export const IssueLabelSelect: React.FC<Props> = ({ setIsOpen, value, onChange,
|
|||||||
<span>{label.name}</span>
|
<span>{label.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center rounded p-1">
|
<div className="flex items-center justify-center rounded p-1">
|
||||||
<CheckIcon
|
<CheckIcon className={`h-3 w-3 ${selected ? "opacity-100" : "opacity-0"}`} />
|
||||||
className={`h-3 w-3 ${
|
|
||||||
selected ? "opacity-100" : "opacity-0"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -168,11 +148,7 @@ export const IssueLabelSelect: React.FC<Props> = ({ setIsOpen, value, onChange,
|
|||||||
<span>{child.name}</span>
|
<span>{child.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center rounded p-1">
|
<div className="flex items-center justify-center rounded p-1">
|
||||||
<CheckIcon
|
<CheckIcon className={`h-3 w-3 ${selected ? "opacity-100" : "opacity-0"}`} />
|
||||||
className={`h-3 w-3 ${
|
|
||||||
selected ? "opacity-100" : "opacity-0"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -5,7 +5,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import stateService from "services/state.service";
|
import stateService from "services/project_state.service";
|
||||||
// ui
|
// ui
|
||||||
import { CustomSearchSelect } from "components/ui";
|
import { CustomSearchSelect } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
@ -30,9 +30,7 @@ export const IssueStateSelect: React.FC<Props> = ({ setIsOpen, value, onChange,
|
|||||||
|
|
||||||
const { data: stateGroups } = useSWR(
|
const { data: stateGroups } = useSWR(
|
||||||
workspaceSlug && projectId ? STATES_LIST(projectId) : null,
|
workspaceSlug && projectId ? STATES_LIST(projectId) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? () => stateService.getStates(workspaceSlug as string, projectId) : null
|
||||||
? () => stateService.getStates(workspaceSlug as string, projectId)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
const states = getStatesList(stateGroups);
|
const states = getStatesList(stateGroups);
|
||||||
|
|
||||||
@ -60,10 +58,7 @@ export const IssueStateSelect: React.FC<Props> = ({ setIsOpen, value, onChange,
|
|||||||
{selectedOption ? (
|
{selectedOption ? (
|
||||||
<StateGroupIcon stateGroup={selectedOption.group} color={selectedOption.color} />
|
<StateGroupIcon stateGroup={selectedOption.group} color={selectedOption.color} />
|
||||||
) : currentDefaultState ? (
|
) : currentDefaultState ? (
|
||||||
<StateGroupIcon
|
<StateGroupIcon stateGroup={currentDefaultState.group} color={currentDefaultState.color} />
|
||||||
stateGroup={currentDefaultState.group}
|
|
||||||
color={currentDefaultState.color}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<Squares2X2Icon className="h-3.5 w-3.5 text-custom-text-200" />
|
<Squares2X2Icon className="h-3.5 w-3.5 text-custom-text-200" />
|
||||||
)}
|
)}
|
||||||
|
@ -7,7 +7,7 @@ import { UseFormWatch } from "react-hook-form";
|
|||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// components
|
// components
|
||||||
import { ExistingIssuesListModal } from "components/core";
|
import { ExistingIssuesListModal } from "components/core";
|
||||||
// icons
|
// icons
|
||||||
@ -23,12 +23,7 @@ type Props = {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SidebarBlockedSelect: React.FC<Props> = ({
|
export const SidebarBlockedSelect: React.FC<Props> = ({ issueId, submitChanges, watch, disabled = false }) => {
|
||||||
issueId,
|
|
||||||
submitChanges,
|
|
||||||
watch,
|
|
||||||
disabled = false,
|
|
||||||
}) => {
|
|
||||||
const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false);
|
const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false);
|
||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
@ -80,10 +75,7 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
|
|||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
submitChanges({
|
submitChanges({
|
||||||
related_issues: [
|
related_issues: [...watch("related_issues")?.filter((i) => i.relation_type !== "blocked_by"), ...response],
|
||||||
...watch("related_issues")?.filter((i) => i.relation_type !== "blocked_by"),
|
|
||||||
...response,
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -127,9 +119,7 @@ export const SidebarBlockedSelect: React.FC<Props> = ({
|
|||||||
type="button"
|
type="button"
|
||||||
className="opacity-0 duration-300 group-hover:opacity-100"
|
className="opacity-0 duration-300 group-hover:opacity-100"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const updatedRelations = watch("related_issues")?.filter(
|
const updatedRelations = watch("related_issues")?.filter((i) => i.id !== relation.id);
|
||||||
(i) => i.id !== relation.id
|
|
||||||
);
|
|
||||||
|
|
||||||
submitChanges({
|
submitChanges({
|
||||||
related_issues: updatedRelations,
|
related_issues: updatedRelations,
|
||||||
|
@ -10,7 +10,7 @@ import useUser from "hooks/use-user";
|
|||||||
// components
|
// components
|
||||||
import { ExistingIssuesListModal } from "components/core";
|
import { ExistingIssuesListModal } from "components/core";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// icons
|
// icons
|
||||||
import { XMarkIcon } from "@heroicons/react/24/outline";
|
import { XMarkIcon } from "@heroicons/react/24/outline";
|
||||||
import { BlockerIcon } from "components/icons";
|
import { BlockerIcon } from "components/icons";
|
||||||
@ -24,12 +24,7 @@ type Props = {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SidebarBlockerSelect: React.FC<Props> = ({
|
export const SidebarBlockerSelect: React.FC<Props> = ({ issueId, submitChanges, watch, disabled = false }) => {
|
||||||
issueId,
|
|
||||||
submitChanges,
|
|
||||||
watch,
|
|
||||||
disabled = false,
|
|
||||||
}) => {
|
|
||||||
const [isBlockerModalOpen, setIsBlockerModalOpen] = useState(false);
|
const [isBlockerModalOpen, setIsBlockerModalOpen] = useState(false);
|
||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
@ -42,8 +37,7 @@ export const SidebarBlockerSelect: React.FC<Props> = ({
|
|||||||
setIsBlockerModalOpen(false);
|
setIsBlockerModalOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const blockerIssue =
|
const blockerIssue = watch("issue_relations")?.filter((i) => i.relation_type === "blocked_by") || [];
|
||||||
watch("issue_relations")?.filter((i) => i.relation_type === "blocked_by") || [];
|
|
||||||
|
|
||||||
const onSubmit = async (data: ISearchIssueResponse[]) => {
|
const onSubmit = async (data: ISearchIssueResponse[]) => {
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
|
@ -5,7 +5,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
import cyclesService from "services/cycles.service";
|
import cyclesService from "services/cycles.service";
|
||||||
// ui
|
// ui
|
||||||
import { Spinner, CustomSelect, Tooltip } from "components/ui";
|
import { Spinner, CustomSelect, Tooltip } from "components/ui";
|
||||||
@ -22,23 +22,14 @@ type Props = {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SidebarCycleSelect: React.FC<Props> = ({
|
export const SidebarCycleSelect: React.FC<Props> = ({ issueDetail, handleCycleChange, disabled = false }) => {
|
||||||
issueDetail,
|
|
||||||
handleCycleChange,
|
|
||||||
disabled = false,
|
|
||||||
}) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, issueId } = router.query;
|
const { workspaceSlug, projectId, issueId } = router.query;
|
||||||
|
|
||||||
const { data: incompleteCycles } = useSWR(
|
const { data: incompleteCycles } = useSWR(
|
||||||
workspaceSlug && projectId ? INCOMPLETE_CYCLES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? INCOMPLETE_CYCLES_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () =>
|
? () => cyclesService.getCyclesWithParams(workspaceSlug as string, projectId as string, "incomplete")
|
||||||
cyclesService.getCyclesWithParams(
|
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
"incomplete"
|
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -63,21 +54,14 @@ export const SidebarCycleSelect: React.FC<Props> = ({
|
|||||||
<CustomSelect
|
<CustomSelect
|
||||||
customButton={
|
customButton={
|
||||||
<div>
|
<div>
|
||||||
<Tooltip
|
<Tooltip position="left" tooltipContent={`${issueCycle ? issueCycle.cycle_detail.name : "No cycle"}`}>
|
||||||
position="left"
|
|
||||||
tooltipContent={`${issueCycle ? issueCycle.cycle_detail.name : "No cycle"}`}
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 w-full flex ${
|
className={`bg-custom-background-80 text-xs rounded px-2.5 py-0.5 w-full flex ${
|
||||||
disabled ? "cursor-not-allowed" : ""
|
disabled ? "cursor-not-allowed" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span
|
<span className={`truncate ${issueCycle ? "text-custom-text-100" : "text-custom-text-200"}`}>
|
||||||
className={`truncate ${
|
|
||||||
issueCycle ? "text-custom-text-100" : "text-custom-text-200"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{issueCycle ? issueCycle.cycle_detail.name : "No cycle"}
|
{issueCycle ? issueCycle.cycle_detail.name : "No cycle"}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -12,7 +12,7 @@ import { X, CopyPlus } from "lucide-react";
|
|||||||
import { BlockerIcon } from "components/icons";
|
import { BlockerIcon } from "components/icons";
|
||||||
import { ExistingIssuesListModal } from "components/core";
|
import { ExistingIssuesListModal } from "components/core";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// types
|
// types
|
||||||
import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types";
|
import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types";
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import { TwitterPicker } from "react-color";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Listbox, Popover, Transition } from "@headlessui/react";
|
import { Listbox, Popover, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
// ui
|
// ui
|
||||||
@ -66,25 +66,21 @@ export const SidebarLabelSelect: React.FC<Props> = ({
|
|||||||
|
|
||||||
const { data: issueLabels, mutate: issueLabelMutate } = useSWR<IIssueLabels[]>(
|
const { data: issueLabels, mutate: issueLabelMutate } = useSWR<IIssueLabels[]>(
|
||||||
workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null,
|
workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? () => issuesService.getIssueLabels(workspaceSlug as string, projectId as string) : null
|
||||||
? () => issuesService.getIssueLabels(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleNewLabel = async (formData: Partial<IIssueLabels>) => {
|
const handleNewLabel = async (formData: Partial<IIssueLabels>) => {
|
||||||
if (!workspaceSlug || !projectId || isSubmitting) return;
|
if (!workspaceSlug || !projectId || isSubmitting) return;
|
||||||
|
|
||||||
await issuesService
|
await issuesService.createIssueLabel(workspaceSlug as string, projectId as string, formData, user).then((res) => {
|
||||||
.createIssueLabel(workspaceSlug as string, projectId as string, formData, user)
|
reset(defaultValues);
|
||||||
.then((res) => {
|
|
||||||
reset(defaultValues);
|
|
||||||
|
|
||||||
issueLabelMutate((prevData: any) => [...(prevData ?? []), res], false);
|
issueLabelMutate((prevData: any) => [...(prevData ?? []), res], false);
|
||||||
|
|
||||||
submitChanges({ labels_list: [...(issueDetails?.labels ?? []), res.id] });
|
submitChanges({ labels_list: [...(issueDetails?.labels ?? []), res.id] });
|
||||||
|
|
||||||
setCreateLabelForm(false);
|
setCreateLabelForm(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -165,9 +161,7 @@ export const SidebarLabelSelect: React.FC<Props> = ({
|
|||||||
{issueLabels ? (
|
{issueLabels ? (
|
||||||
issueLabels.length > 0 ? (
|
issueLabels.length > 0 ? (
|
||||||
issueLabels.map((label: IIssueLabels) => {
|
issueLabels.map((label: IIssueLabels) => {
|
||||||
const children = issueLabels?.filter(
|
const children = issueLabels?.filter((l) => l.parent === label.id);
|
||||||
(l) => l.parent === label.id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (children.length === 0) {
|
if (children.length === 0) {
|
||||||
if (!label.parent)
|
if (!label.parent)
|
||||||
@ -175,9 +169,7 @@ export const SidebarLabelSelect: React.FC<Props> = ({
|
|||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={label.id}
|
key={label.id}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
`${
|
`${active || selected ? "bg-custom-background-90" : ""} ${
|
||||||
active || selected ? "bg-custom-background-90" : ""
|
|
||||||
} ${
|
|
||||||
selected ? "" : "text-custom-text-200"
|
selected ? "" : "text-custom-text-200"
|
||||||
} flex cursor-pointer select-none items-center gap-2 truncate p-2`
|
} flex cursor-pointer select-none items-center gap-2 truncate p-2`
|
||||||
}
|
}
|
||||||
@ -186,10 +178,7 @@ export const SidebarLabelSelect: React.FC<Props> = ({
|
|||||||
<span
|
<span
|
||||||
className="h-2 w-2 flex-shrink-0 rounded-full"
|
className="h-2 w-2 flex-shrink-0 rounded-full"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor:
|
backgroundColor: label.color && label.color !== "" ? label.color : "#000",
|
||||||
label.color && label.color !== ""
|
|
||||||
? label.color
|
|
||||||
: "#000",
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{label.name}
|
{label.name}
|
||||||
@ -207,11 +196,7 @@ export const SidebarLabelSelect: React.FC<Props> = ({
|
|||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={child.id}
|
key={child.id}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
`${
|
`${active || selected ? "bg-custom-background-100" : ""} ${
|
||||||
active || selected
|
|
||||||
? "bg-custom-background-100"
|
|
||||||
: ""
|
|
||||||
} ${
|
|
||||||
selected ? "" : "text-custom-text-200"
|
selected ? "" : "text-custom-text-200"
|
||||||
} flex cursor-pointer select-none items-center gap-2 truncate p-2`
|
} flex cursor-pointer select-none items-center gap-2 truncate p-2`
|
||||||
}
|
}
|
||||||
@ -248,9 +233,7 @@ export const SidebarLabelSelect: React.FC<Props> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`flex ${
|
className={`flex ${
|
||||||
isNotAllowed || uneditable
|
isNotAllowed || uneditable ? "cursor-not-allowed" : "cursor-pointer hover:bg-custom-background-90"
|
||||||
? "cursor-not-allowed"
|
|
||||||
: "cursor-pointer hover:bg-custom-background-90"
|
|
||||||
} items-center gap-1 rounded-2xl border border-custom-border-100 px-2 py-0.5 text-xs text-custom-text-200`}
|
} items-center gap-1 rounded-2xl border border-custom-border-100 px-2 py-0.5 text-xs text-custom-text-200`}
|
||||||
onClick={() => setCreateLabelForm((prevData) => !prevData)}
|
onClick={() => setCreateLabelForm((prevData) => !prevData)}
|
||||||
disabled={uneditable}
|
disabled={uneditable}
|
||||||
@ -326,11 +309,7 @@ export const SidebarLabelSelect: React.FC<Props> = ({
|
|||||||
>
|
>
|
||||||
<XMarkIcon className="h-4 w-4 text-white" />
|
<XMarkIcon className="h-4 w-4 text-white" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button type="submit" className="grid place-items-center rounded bg-green-500 p-2.5" disabled={isSubmitting}>
|
||||||
type="submit"
|
|
||||||
className="grid place-items-center rounded bg-green-500 p-2.5"
|
|
||||||
disabled={isSubmitting}
|
|
||||||
>
|
|
||||||
<PlusIcon className="h-4 w-4 text-white" />
|
<PlusIcon className="h-4 w-4 text-white" />
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -12,7 +12,7 @@ import { BlockerIcon, RelatedIcon } from "components/icons";
|
|||||||
// components
|
// components
|
||||||
import { ExistingIssuesListModal } from "components/core";
|
import { ExistingIssuesListModal } from "components/core";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// types
|
// types
|
||||||
import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types";
|
import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types";
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import stateService from "services/state.service";
|
import stateService from "services/project_state.service";
|
||||||
// ui
|
// ui
|
||||||
import { Spinner, CustomSelect } from "components/ui";
|
import { Spinner, CustomSelect } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
@ -28,9 +28,7 @@ export const SidebarStateSelect: React.FC<Props> = ({ value, onChange, disabled
|
|||||||
|
|
||||||
const { data: stateGroups } = useSWR(
|
const { data: stateGroups } = useSWR(
|
||||||
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? () => stateService.getStates(workspaceSlug as string, projectId as string) : null
|
||||||
? () => stateService.getStates(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
const states = getStatesList(stateGroups);
|
const states = getStatesList(stateGroups);
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import useUserAuth from "hooks/use-user-auth";
|
|||||||
import useUserIssueNotificationSubscription from "hooks/use-issue-notification-subscription";
|
import useUserIssueNotificationSubscription from "hooks/use-issue-notification-subscription";
|
||||||
import useEstimateOption from "hooks/use-estimate-option";
|
import useEstimateOption from "hooks/use-estimate-option";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
// contexts
|
// contexts
|
||||||
import { useProjectMyMembership } from "contexts/project-member.context";
|
import { useProjectMyMembership } from "contexts/project-member.context";
|
||||||
@ -103,8 +103,11 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
|
|
||||||
const { isEstimateActive } = useEstimateOption();
|
const { isEstimateActive } = useEstimateOption();
|
||||||
|
|
||||||
const { loading, handleSubscribe, handleUnsubscribe, subscribed } =
|
const { loading, handleSubscribe, handleUnsubscribe, subscribed } = useUserIssueNotificationSubscription(
|
||||||
useUserIssueNotificationSubscription(workspaceSlug, projectId, issueId);
|
workspaceSlug,
|
||||||
|
projectId,
|
||||||
|
issueId
|
||||||
|
);
|
||||||
|
|
||||||
const { memberRole } = useProjectMyMembership();
|
const { memberRole } = useProjectMyMembership();
|
||||||
|
|
||||||
@ -198,13 +201,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
await issuesService
|
await issuesService
|
||||||
.updateIssueLink(
|
.updateIssueLink(workspaceSlug as string, projectId as string, issueDetail.id, linkId, payload)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
issueDetail.id,
|
|
||||||
linkId,
|
|
||||||
payload
|
|
||||||
)
|
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
mutate(ISSUE_DETAILS(issueDetail.id));
|
mutate(ISSUE_DETAILS(issueDetail.id));
|
||||||
})
|
})
|
||||||
@ -235,12 +232,9 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyText = () => {
|
const handleCopyText = () => {
|
||||||
const originURL =
|
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
||||||
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
|
||||||
|
|
||||||
copyTextToClipboard(
|
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issueDetail?.id}`).then(() => {
|
||||||
`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${issueDetail?.id}`
|
|
||||||
).then(() => {
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Link Copied!",
|
title: "Link Copied!",
|
||||||
@ -264,9 +258,7 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
fieldsToShow.includes("dueDate");
|
fieldsToShow.includes("dueDate");
|
||||||
|
|
||||||
const showThirdSection =
|
const showThirdSection =
|
||||||
fieldsToShow.includes("all") ||
|
fieldsToShow.includes("all") || fieldsToShow.includes("cycle") || fieldsToShow.includes("module");
|
||||||
fieldsToShow.includes("cycle") ||
|
|
||||||
fieldsToShow.includes("module");
|
|
||||||
|
|
||||||
const startDate = watchIssue("start_date");
|
const startDate = watchIssue("start_date");
|
||||||
const targetDate = watchIssue("target_date");
|
const targetDate = watchIssue("target_date");
|
||||||
@ -413,30 +405,27 @@ export const IssueDetailsSidebar: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("estimate")) &&
|
{(fieldsToShow.includes("all") || fieldsToShow.includes("estimate")) && isEstimateActive && (
|
||||||
isEstimateActive && (
|
<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">
|
<PlayIcon className="h-4 w-4 flex-shrink-0 -rotate-90" />
|
||||||
<PlayIcon className="h-4 w-4 flex-shrink-0 -rotate-90" />
|
<p>Estimate</p>
|
||||||
<p>Estimate</p>
|
|
||||||
</div>
|
|
||||||
<div className="sm:basis-1/2">
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="estimate_point"
|
|
||||||
render={({ field: { value } }) => (
|
|
||||||
<SidebarEstimateSelect
|
|
||||||
value={value}
|
|
||||||
onChange={(val: number | null) =>
|
|
||||||
submitChanges({ estimate_point: val })
|
|
||||||
}
|
|
||||||
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="sm:basis-1/2">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="estimate_point"
|
||||||
|
render={({ field: { value } }) => (
|
||||||
|
<SidebarEstimateSelect
|
||||||
|
value={value}
|
||||||
|
onChange={(val: number | null) => submitChanges({ estimate_point: val })}
|
||||||
|
disabled={memberRole.isGuest || memberRole.isViewer || uneditable}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{showSecondSection && (
|
{showSecondSection && (
|
||||||
|
@ -8,7 +8,7 @@ import useSWR, { mutate } from "swr";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Disclosure, Transition } from "@headlessui/react";
|
import { Disclosure, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// contexts
|
// contexts
|
||||||
import { useProjectMyMembership } from "contexts/project-member.context";
|
import { useProjectMyMembership } from "contexts/project-member.context";
|
||||||
// components
|
// components
|
||||||
@ -126,10 +126,7 @@ export const SubIssuesList: FC<Props> = ({ parentIssue, user, disabled = false }
|
|||||||
<div className="flex items-center justify-start gap-3 text-custom-text-200">
|
<div className="flex items-center justify-start gap-3 text-custom-text-200">
|
||||||
<Disclosure.Button className="flex items-center gap-1 rounded px-2 py-1 text-xs text-custom-text-100 hover:bg-custom-background-80">
|
<Disclosure.Button className="flex items-center gap-1 rounded px-2 py-1 text-xs text-custom-text-100 hover:bg-custom-background-80">
|
||||||
<ChevronRightIcon className={`h-3 w-3 ${open ? "rotate-90" : ""}`} />
|
<ChevronRightIcon className={`h-3 w-3 ${open ? "rotate-90" : ""}`} />
|
||||||
Sub-issues{" "}
|
Sub-issues <span className="ml-1 text-custom-text-200">{subIssuesResponse.sub_issues.length}</span>
|
||||||
<span className="ml-1 text-custom-text-200">
|
|
||||||
{subIssuesResponse.sub_issues.length}
|
|
||||||
</span>
|
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
<div className="flex w-60 items-center gap-2">
|
<div className="flex w-60 items-center gap-2">
|
||||||
<div className="bar relative h-1.5 w-full rounded bg-custom-background-80">
|
<div className="bar relative h-1.5 w-full rounded bg-custom-background-80">
|
||||||
@ -186,10 +183,7 @@ export const SubIssuesList: FC<Props> = ({ parentIssue, user, disabled = false }
|
|||||||
>
|
>
|
||||||
<Disclosure.Panel className="mt-3 flex flex-col gap-y-1">
|
<Disclosure.Panel className="mt-3 flex flex-col gap-y-1">
|
||||||
{subIssuesResponse.sub_issues.map((issue) => (
|
{subIssuesResponse.sub_issues.map((issue) => (
|
||||||
<Link
|
<Link key={issue.id} href={`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`}>
|
||||||
key={issue.id}
|
|
||||||
href={`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`}
|
|
||||||
>
|
|
||||||
<a className="group flex items-center justify-between gap-2 rounded p-2 hover:bg-custom-background-90">
|
<a className="group flex items-center justify-between gap-2 rounded p-2 hover:bg-custom-background-90">
|
||||||
<div className="flex items-center gap-2 rounded text-xs">
|
<div className="flex items-center gap-2 rounded text-xs">
|
||||||
<span
|
<span
|
||||||
@ -240,9 +234,7 @@ export const SubIssuesList: FC<Props> = ({ parentIssue, user, disabled = false }
|
|||||||
noChevron
|
noChevron
|
||||||
>
|
>
|
||||||
<CustomMenu.MenuItem onClick={handleCreateIssueModal}>Create new</CustomMenu.MenuItem>
|
<CustomMenu.MenuItem onClick={handleCreateIssueModal}>Create new</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem onClick={() => setSubIssuesListModal(true)}>
|
<CustomMenu.MenuItem onClick={() => setSubIssuesListModal(true)}>Add an existing issue</CustomMenu.MenuItem>
|
||||||
Add an existing issue
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
@ -6,7 +6,7 @@ import useSWR from "swr";
|
|||||||
|
|
||||||
// services
|
// services
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
import trackEventServices from "services/track-event.service";
|
import trackEventServices from "services/track_event.service";
|
||||||
// ui
|
// ui
|
||||||
import { AssigneesList, Avatar, CustomSearchSelect, Icon, Tooltip } from "components/ui";
|
import { AssigneesList, Avatar, CustomSearchSelect, Icon, Tooltip } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
|
@ -5,7 +5,7 @@ import { CustomDatePicker, Tooltip } from "components/ui";
|
|||||||
// helpers
|
// helpers
|
||||||
import { findHowManyDaysLeft, renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
import { findHowManyDaysLeft, renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
||||||
// services
|
// services
|
||||||
import trackEventServices from "services/track-event.service";
|
import trackEventServices from "services/track_event.service";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IIssue } from "types";
|
import { ICurrentUserResponse, IIssue } from "types";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
@ -42,9 +42,7 @@ export const ViewDueDateSelect: React.FC<Props> = ({
|
|||||||
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"
|
|
||||||
}
|
|
||||||
position={tooltipPosition}
|
position={tooltipPosition}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -80,9 +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"} ${
|
||||||
displayFilters.layout === "kanban"
|
displayFilters.layout === "kanban" ? "bg-custom-background-90" : "bg-custom-background-100"
|
||||||
? "bg-custom-background-90"
|
|
||||||
: "bg-custom-background-100"
|
|
||||||
}`}
|
}`}
|
||||||
minDate={minDate ?? undefined}
|
minDate={minDate ?? undefined}
|
||||||
noBorder={noBorder}
|
noBorder={noBorder}
|
||||||
|
@ -3,7 +3,7 @@ import React from "react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import trackEventServices from "services/track-event.service";
|
import trackEventServices from "services/track_event.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useEstimateOption from "hooks/use-estimate-option";
|
import useEstimateOption from "hooks/use-estimate-option";
|
||||||
// ui
|
// ui
|
||||||
|
@ -5,7 +5,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// component
|
// component
|
||||||
import { CreateLabelModal } from "components/labels";
|
import { CreateLabelModal } from "components/labels";
|
||||||
// ui
|
// ui
|
||||||
@ -45,9 +45,7 @@ export const ViewLabelSelect: React.FC<Props> = ({
|
|||||||
|
|
||||||
const { data: issueLabels } = useSWR<IIssueLabels[]>(
|
const { data: issueLabels } = useSWR<IIssueLabels[]>(
|
||||||
projectId ? PROJECT_ISSUE_LABELS(projectId.toString()) : null,
|
projectId ? PROJECT_ISSUE_LABELS(projectId.toString()) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? () => issuesService.getIssueLabels(workspaceSlug as string, projectId as string) : null
|
||||||
? () => issuesService.getIssueLabels(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const options = issueLabels?.map((label) => ({
|
const options = issueLabels?.map((label) => ({
|
||||||
|
@ -3,7 +3,7 @@ import React from "react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import trackEventServices from "services/track-event.service";
|
import trackEventServices from "services/track_event.service";
|
||||||
// ui
|
// ui
|
||||||
import { CustomSelect, Tooltip } from "components/ui";
|
import { CustomSelect, Tooltip } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
@ -61,9 +61,9 @@ export const ViewPrioritySelect: React.FC<Props> = ({
|
|||||||
customButton={
|
customButton={
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`grid place-items-center rounded ${
|
className={`grid place-items-center rounded ${isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"} ${
|
||||||
isNotAllowed ? "cursor-not-allowed" : "cursor-pointer"
|
noBorder ? "" : "h-6 w-6 border shadow-sm"
|
||||||
} ${noBorder ? "" : "h-6 w-6 border shadow-sm"} ${
|
} ${
|
||||||
noBorder
|
noBorder
|
||||||
? ""
|
? ""
|
||||||
: issue.priority === "urgent"
|
: issue.priority === "urgent"
|
||||||
@ -71,11 +71,7 @@ export const ViewPrioritySelect: React.FC<Props> = ({
|
|||||||
: "border-custom-border-300 bg-custom-background-100"
|
: "border-custom-border-300 bg-custom-background-100"
|
||||||
} items-center`}
|
} items-center`}
|
||||||
>
|
>
|
||||||
<Tooltip
|
<Tooltip tooltipHeading="Priority" tooltipContent={issue.priority ?? "None"} position={tooltipPosition}>
|
||||||
tooltipHeading="Priority"
|
|
||||||
tooltipContent={issue.priority ?? "None"}
|
|
||||||
position={tooltipPosition}
|
|
||||||
>
|
|
||||||
<span className="flex gap-1 items-center text-custom-text-200 text-xs">
|
<span className="flex gap-1 items-center text-custom-text-200 text-xs">
|
||||||
<PriorityIcon
|
<PriorityIcon
|
||||||
priority={issue.priority}
|
priority={issue.priority}
|
||||||
|
@ -5,7 +5,7 @@ import { CustomDatePicker, Tooltip } from "components/ui";
|
|||||||
// helpers
|
// helpers
|
||||||
import { findHowManyDaysLeft, renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
import { findHowManyDaysLeft, renderShortDateWithYearFormat } from "helpers/date-time.helper";
|
||||||
// services
|
// services
|
||||||
import trackEventServices from "services/track-event.service";
|
import trackEventServices from "services/track_event.service";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IIssue } from "types";
|
import { ICurrentUserResponse, IIssue } from "types";
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
@ -42,9 +42,7 @@ export const ViewStartDateSelect: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
tooltipHeading="Start date"
|
tooltipHeading="Start date"
|
||||||
tooltipContent={
|
tooltipContent={issue.start_date ? renderShortDateWithYearFormat(issue.start_date) ?? "N/A" : "N/A"}
|
||||||
issue.start_date ? renderShortDateWithYearFormat(issue.start_date) ?? "N/A" : "N/A"
|
|
||||||
}
|
|
||||||
position={tooltipPosition}
|
position={tooltipPosition}
|
||||||
>
|
>
|
||||||
<div className="group flex-shrink-0 relative max-w-[6.5rem]">
|
<div className="group flex-shrink-0 relative max-w-[6.5rem]">
|
||||||
@ -72,9 +70,7 @@ export const ViewStartDateSelect: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className={`${issue?.start_date ? "w-[6.5rem]" : "w-[5rem] text-center"} ${
|
className={`${issue?.start_date ? "w-[6.5rem]" : "w-[5rem] text-center"} ${
|
||||||
displayFilters.layout === "kanban"
|
displayFilters.layout === "kanban" ? "bg-custom-background-90" : "bg-custom-background-100"
|
||||||
? "bg-custom-background-90"
|
|
||||||
: "bg-custom-background-100"
|
|
||||||
}`}
|
}`}
|
||||||
maxDate={maxDate ?? undefined}
|
maxDate={maxDate ?? undefined}
|
||||||
noBorder={noBorder}
|
noBorder={noBorder}
|
||||||
|
@ -5,8 +5,8 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import stateService from "services/state.service";
|
import stateService from "services/project_state.service";
|
||||||
import trackEventServices from "services/track-event.service";
|
import trackEventServices from "services/track_event.service";
|
||||||
// ui
|
// ui
|
||||||
import { CustomSearchSelect, Tooltip } from "components/ui";
|
import { CustomSearchSelect, Tooltip } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
@ -48,9 +48,7 @@ export const ViewStateSelect: React.FC<Props> = ({
|
|||||||
|
|
||||||
const { data: stateGroups } = useSWR(
|
const { data: stateGroups } = useSWR(
|
||||||
workspaceSlug && issue && fetchStates ? STATES_LIST(issue.project) : null,
|
workspaceSlug && issue && fetchStates ? STATES_LIST(issue.project) : null,
|
||||||
workspaceSlug && issue && fetchStates
|
workspaceSlug && issue && fetchStates ? () => stateService.getStates(workspaceSlug as string, issue.project) : null
|
||||||
? () => stateService.getStates(workspaceSlug as string, issue.project)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
const states = getStatesList(stateGroups);
|
const states = getStatesList(stateGroups);
|
||||||
|
|
||||||
@ -68,16 +66,10 @@ export const ViewStateSelect: React.FC<Props> = ({
|
|||||||
const selectedOption = issue.state_detail;
|
const selectedOption = issue.state_detail;
|
||||||
|
|
||||||
const stateLabel = (
|
const stateLabel = (
|
||||||
<Tooltip
|
<Tooltip tooltipHeading="State" tooltipContent={selectedOption?.name ?? ""} position={tooltipPosition}>
|
||||||
tooltipHeading="State"
|
|
||||||
tooltipContent={selectedOption?.name ?? ""}
|
|
||||||
position={tooltipPosition}
|
|
||||||
>
|
|
||||||
<div className="flex items-center cursor-pointer w-full gap-2 text-custom-text-200">
|
<div className="flex items-center cursor-pointer w-full gap-2 text-custom-text-200">
|
||||||
<span className="h-3.5 w-3.5">
|
<span className="h-3.5 w-3.5">
|
||||||
{selectedOption && (
|
{selectedOption && <StateGroupIcon stateGroup={selectedOption.group} color={selectedOption.color} />}
|
||||||
<StateGroupIcon stateGroup={selectedOption.group} color={selectedOption.color} />
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
<span className="truncate">{selectedOption?.name ?? "State"}</span>
|
<span className="truncate">{selectedOption?.name ?? "State"}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,7 +11,7 @@ import { TwitterPicker } from "react-color";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Popover, Transition } from "@headlessui/react";
|
import { Dialog, Popover, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// ui
|
// ui
|
||||||
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
|
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
@ -36,13 +36,7 @@ const defaultValues: Partial<IState> = {
|
|||||||
color: "rgb(var(--color-text-200))",
|
color: "rgb(var(--color-text-200))",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateLabelModal: React.FC<Props> = ({
|
export const CreateLabelModal: React.FC<Props> = ({ isOpen, projectId, handleClose, user, onSuccess }) => {
|
||||||
isOpen,
|
|
||||||
projectId,
|
|
||||||
handleClose,
|
|
||||||
user,
|
|
||||||
onSuccess,
|
|
||||||
}) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
|
|
||||||
@ -73,11 +67,7 @@ export const CreateLabelModal: React.FC<Props> = ({
|
|||||||
await issuesService
|
await issuesService
|
||||||
.createIssueLabel(workspaceSlug as string, projectId as string, formData, user)
|
.createIssueLabel(workspaceSlug as string, projectId as string, formData, user)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
mutate<IIssueLabels[]>(
|
mutate<IIssueLabels[]>(PROJECT_ISSUE_LABELS(projectId), (prevData) => [res, ...(prevData ?? [])], false);
|
||||||
PROJECT_ISSUE_LABELS(projectId),
|
|
||||||
(prevData) => [res, ...(prevData ?? [])],
|
|
||||||
false
|
|
||||||
);
|
|
||||||
onClose();
|
onClose();
|
||||||
if (onSuccess) onSuccess(res);
|
if (onSuccess) onSuccess(res);
|
||||||
})
|
})
|
||||||
@ -115,10 +105,7 @@ export const CreateLabelModal: React.FC<Props> = ({
|
|||||||
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-90 px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
|
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-90 px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div>
|
<div>
|
||||||
<Dialog.Title
|
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
|
||||||
as="h3"
|
|
||||||
className="text-lg font-medium leading-6 text-custom-text-100"
|
|
||||||
>
|
|
||||||
Create Label
|
Create Label
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-8 flex items-center gap-2">
|
<div className="mt-8 flex items-center gap-2">
|
||||||
|
@ -13,7 +13,7 @@ import { TwitterPicker } from "react-color";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Popover, Transition } from "@headlessui/react";
|
import { Popover, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// ui
|
// ui
|
||||||
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
|
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
|
||||||
// types
|
// types
|
||||||
@ -35,174 +35,157 @@ const defaultValues: Partial<IIssueLabels> = {
|
|||||||
color: "rgb(var(--color-text-200))",
|
color: "rgb(var(--color-text-200))",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateUpdateLabelInline = forwardRef<HTMLDivElement, Props>(
|
export const CreateUpdateLabelInline = forwardRef<HTMLDivElement, Props>(function CreateUpdateLabelInline(props, ref) {
|
||||||
function CreateUpdateLabelInline(props, ref) {
|
const { labelForm, setLabelForm, isUpdating, labelToUpdate, onClose } = props;
|
||||||
const { labelForm, setLabelForm, isUpdating, labelToUpdate, onClose } = props;
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
const { user } = useUserAuth();
|
const { user } = useUserAuth();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
control,
|
control,
|
||||||
register,
|
register,
|
||||||
reset,
|
reset,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
watch,
|
watch,
|
||||||
setValue,
|
setValue,
|
||||||
} = useForm<IIssueLabels>({
|
} = useForm<IIssueLabels>({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setLabelForm(false);
|
setLabelForm(false);
|
||||||
reset(defaultValues);
|
reset(defaultValues);
|
||||||
if (onClose) onClose();
|
if (onClose) onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLabelCreate: SubmitHandler<IIssueLabels> = async (formData) => {
|
const handleLabelCreate: SubmitHandler<IIssueLabels> = async (formData) => {
|
||||||
if (!workspaceSlug || !projectId || isSubmitting) return;
|
if (!workspaceSlug || !projectId || isSubmitting) return;
|
||||||
|
|
||||||
await issuesService
|
await issuesService.createIssueLabel(workspaceSlug as string, projectId as string, formData, user).then((res) => {
|
||||||
.createIssueLabel(workspaceSlug as string, projectId as string, formData, user)
|
mutate<IIssueLabels[]>(
|
||||||
.then((res) => {
|
PROJECT_ISSUE_LABELS(projectId as string),
|
||||||
mutate<IIssueLabels[]>(
|
(prevData) => [res, ...(prevData ?? [])],
|
||||||
PROJECT_ISSUE_LABELS(projectId as string),
|
false
|
||||||
(prevData) => [res, ...(prevData ?? [])],
|
|
||||||
false
|
|
||||||
);
|
|
||||||
handleClose();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLabelUpdate: SubmitHandler<IIssueLabels> = async (formData) => {
|
|
||||||
if (!workspaceSlug || !projectId || isSubmitting) return;
|
|
||||||
|
|
||||||
await issuesService
|
|
||||||
.patchIssueLabel(
|
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
labelToUpdate?.id ?? "",
|
|
||||||
formData,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
reset(defaultValues);
|
|
||||||
mutate<IIssueLabels[]>(
|
|
||||||
PROJECT_ISSUE_LABELS(projectId as string),
|
|
||||||
(prevData) =>
|
|
||||||
prevData?.map((p) => (p.id === labelToUpdate?.id ? { ...p, ...formData } : p)),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
handleClose();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!labelForm && isUpdating) return;
|
|
||||||
|
|
||||||
reset();
|
|
||||||
}, [labelForm, isUpdating, reset]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!labelToUpdate) return;
|
|
||||||
|
|
||||||
setValue(
|
|
||||||
"color",
|
|
||||||
labelToUpdate.color && labelToUpdate.color !== "" ? labelToUpdate.color : "#000"
|
|
||||||
);
|
);
|
||||||
setValue("name", labelToUpdate.name);
|
handleClose();
|
||||||
}, [labelToUpdate, setValue]);
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleLabelUpdate: SubmitHandler<IIssueLabels> = async (formData) => {
|
||||||
if (labelToUpdate) {
|
if (!workspaceSlug || !projectId || isSubmitting) return;
|
||||||
setValue(
|
|
||||||
"color",
|
await issuesService
|
||||||
labelToUpdate.color && labelToUpdate.color !== "" ? labelToUpdate.color : "#000"
|
.patchIssueLabel(workspaceSlug as string, projectId as string, labelToUpdate?.id ?? "", formData, user)
|
||||||
|
.then(() => {
|
||||||
|
reset(defaultValues);
|
||||||
|
mutate<IIssueLabels[]>(
|
||||||
|
PROJECT_ISSUE_LABELS(projectId as string),
|
||||||
|
(prevData) => prevData?.map((p) => (p.id === labelToUpdate?.id ? { ...p, ...formData } : p)),
|
||||||
|
false
|
||||||
);
|
);
|
||||||
return;
|
handleClose();
|
||||||
}
|
});
|
||||||
|
};
|
||||||
|
|
||||||
setValue("color", getRandomLabelColor());
|
useEffect(() => {
|
||||||
}, [labelToUpdate, setValue]);
|
if (!labelForm && isUpdating) return;
|
||||||
|
|
||||||
return (
|
reset();
|
||||||
<div
|
}, [labelForm, isUpdating, reset]);
|
||||||
className={`flex scroll-m-8 items-center gap-2 rounded border border-custom-border-200 bg-custom-background-100 px-3.5 py-2 ${
|
|
||||||
labelForm ? "" : "hidden"
|
useEffect(() => {
|
||||||
}`}
|
if (!labelToUpdate) return;
|
||||||
ref={ref}
|
|
||||||
>
|
setValue("color", labelToUpdate.color && labelToUpdate.color !== "" ? labelToUpdate.color : "#000");
|
||||||
<div className="flex-shrink-0">
|
setValue("name", labelToUpdate.name);
|
||||||
<Popover className="relative z-10 flex h-full w-full items-center justify-center">
|
}, [labelToUpdate, setValue]);
|
||||||
{({ open }) => (
|
|
||||||
<>
|
useEffect(() => {
|
||||||
<Popover.Button
|
if (labelToUpdate) {
|
||||||
className={`group inline-flex items-center text-base font-medium focus:outline-none ${
|
setValue("color", labelToUpdate.color && labelToUpdate.color !== "" ? labelToUpdate.color : "#000");
|
||||||
open ? "text-custom-text-100" : "text-custom-text-200"
|
return;
|
||||||
}`}
|
}
|
||||||
>
|
|
||||||
<span
|
setValue("color", getRandomLabelColor());
|
||||||
className="h-4 w-4 rounded-full"
|
}, [labelToUpdate, setValue]);
|
||||||
style={{
|
|
||||||
backgroundColor: watch("color"),
|
return (
|
||||||
}}
|
<div
|
||||||
|
className={`flex scroll-m-8 items-center gap-2 rounded border border-custom-border-200 bg-custom-background-100 px-3.5 py-2 ${
|
||||||
|
labelForm ? "" : "hidden"
|
||||||
|
}`}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<Popover className="relative z-10 flex h-full w-full items-center justify-center">
|
||||||
|
{({ open }) => (
|
||||||
|
<>
|
||||||
|
<Popover.Button
|
||||||
|
className={`group inline-flex items-center text-base font-medium focus:outline-none ${
|
||||||
|
open ? "text-custom-text-100" : "text-custom-text-200"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="h-4 w-4 rounded-full"
|
||||||
|
style={{
|
||||||
|
backgroundColor: watch("color"),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Popover.Button>
|
||||||
|
|
||||||
|
<Transition
|
||||||
|
as={React.Fragment}
|
||||||
|
enter="transition ease-out duration-200"
|
||||||
|
enterFrom="opacity-0 translate-y-1"
|
||||||
|
enterTo="opacity-100 translate-y-0"
|
||||||
|
leave="transition ease-in duration-150"
|
||||||
|
leaveFrom="opacity-100 translate-y-0"
|
||||||
|
leaveTo="opacity-0 translate-y-1"
|
||||||
|
>
|
||||||
|
<Popover.Panel className="absolute top-full left-0 z-20 mt-3 w-screen max-w-xs px-2 sm:px-0">
|
||||||
|
<Controller
|
||||||
|
name="color"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<TwitterPicker
|
||||||
|
colors={LABEL_COLOR_OPTIONS}
|
||||||
|
color={value}
|
||||||
|
onChange={(value) => onChange(value.hex)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Popover.Button>
|
</Popover.Panel>
|
||||||
|
</Transition>
|
||||||
<Transition
|
</>
|
||||||
as={React.Fragment}
|
)}
|
||||||
enter="transition ease-out duration-200"
|
</Popover>
|
||||||
enterFrom="opacity-0 translate-y-1"
|
|
||||||
enterTo="opacity-100 translate-y-0"
|
|
||||||
leave="transition ease-in duration-150"
|
|
||||||
leaveFrom="opacity-100 translate-y-0"
|
|
||||||
leaveTo="opacity-0 translate-y-1"
|
|
||||||
>
|
|
||||||
<Popover.Panel className="absolute top-full left-0 z-20 mt-3 w-screen max-w-xs px-2 sm:px-0">
|
|
||||||
<Controller
|
|
||||||
name="color"
|
|
||||||
control={control}
|
|
||||||
render={({ field: { value, onChange } }) => (
|
|
||||||
<TwitterPicker
|
|
||||||
colors={LABEL_COLOR_OPTIONS}
|
|
||||||
color={value}
|
|
||||||
onChange={(value) => onChange(value.hex)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Popover.Panel>
|
|
||||||
</Transition>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-1 flex-col justify-center">
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
id="labelName"
|
|
||||||
name="name"
|
|
||||||
register={register}
|
|
||||||
placeholder="Label title"
|
|
||||||
validations={{
|
|
||||||
required: "Label title is required",
|
|
||||||
}}
|
|
||||||
error={errors.name}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<SecondaryButton onClick={() => handleClose()}>Cancel</SecondaryButton>
|
|
||||||
{isUpdating ? (
|
|
||||||
<PrimaryButton onClick={handleSubmit(handleLabelUpdate)} loading={isSubmitting}>
|
|
||||||
{isSubmitting ? "Updating" : "Update"}
|
|
||||||
</PrimaryButton>
|
|
||||||
) : (
|
|
||||||
<PrimaryButton onClick={handleSubmit(handleLabelCreate)} loading={isSubmitting}>
|
|
||||||
{isSubmitting ? "Adding" : "Add"}
|
|
||||||
</PrimaryButton>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
<div className="flex flex-1 flex-col justify-center">
|
||||||
}
|
<Input
|
||||||
);
|
type="text"
|
||||||
|
id="labelName"
|
||||||
|
name="name"
|
||||||
|
register={register}
|
||||||
|
placeholder="Label title"
|
||||||
|
validations={{
|
||||||
|
required: "Label title is required",
|
||||||
|
}}
|
||||||
|
error={errors.name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<SecondaryButton onClick={() => handleClose()}>Cancel</SecondaryButton>
|
||||||
|
{isUpdating ? (
|
||||||
|
<PrimaryButton onClick={handleSubmit(handleLabelUpdate)} loading={isSubmitting}>
|
||||||
|
{isSubmitting ? "Updating" : "Update"}
|
||||||
|
</PrimaryButton>
|
||||||
|
) : (
|
||||||
|
<PrimaryButton onClick={handleSubmit(handleLabelCreate)} loading={isSubmitting}>
|
||||||
|
{isSubmitting ? "Adding" : "Add"}
|
||||||
|
</PrimaryButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@ -9,7 +9,7 @@ import { Dialog, Transition } from "@headlessui/react";
|
|||||||
// icons
|
// icons
|
||||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
@ -95,23 +95,17 @@ export const DeleteLabelModal: React.FC<Props> = ({ isOpen, onClose, data, user
|
|||||||
<div className="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
<div className="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
<div className="sm:flex sm:items-start">
|
<div className="sm:flex sm:items-start">
|
||||||
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
<ExclamationTriangleIcon
|
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
|
||||||
className="h-6 w-6 text-red-600"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
<Dialog.Title
|
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
|
||||||
as="h3"
|
|
||||||
className="text-lg font-medium leading-6 text-custom-text-100"
|
|
||||||
>
|
|
||||||
Delete Label
|
Delete Label
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-sm text-custom-text-200">
|
<p className="text-sm text-custom-text-200">
|
||||||
Are you sure you want to delete label-{" "}
|
Are you sure you want to delete label-{" "}
|
||||||
<span className="font-medium text-custom-text-100">{data?.name}</span>?
|
<span className="font-medium text-custom-text-100">{data?.name}</span>? The label will be
|
||||||
The label will be removed from all the issues.
|
removed from all the issues.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@ import { Combobox, Dialog, Transition } from "@headlessui/react";
|
|||||||
// icons
|
// icons
|
||||||
import { RectangleStackIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
import { RectangleStackIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IIssueLabels } from "types";
|
import { ICurrentUserResponse, IIssueLabels } from "types";
|
||||||
// constants
|
// constants
|
||||||
@ -30,9 +30,7 @@ export const LabelsListModal: React.FC<Props> = ({ isOpen, handleClose, parent,
|
|||||||
|
|
||||||
const { data: issueLabels, mutate } = useSWR<IIssueLabels[]>(
|
const { data: issueLabels, mutate } = useSWR<IIssueLabels[]>(
|
||||||
workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null,
|
workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? () => issuesService.getIssueLabels(workspaceSlug as string, projectId as string) : null
|
||||||
? () => issuesService.getIssueLabels(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredLabels: IIssueLabels[] =
|
const filteredLabels: IIssueLabels[] =
|
||||||
@ -114,9 +112,7 @@ export const LabelsListModal: React.FC<Props> = ({ isOpen, handleClose, parent,
|
|||||||
{filteredLabels.length > 0 && (
|
{filteredLabels.length > 0 && (
|
||||||
<li className="p-2">
|
<li className="p-2">
|
||||||
{query === "" && (
|
{query === "" && (
|
||||||
<h2 className="mt-4 mb-2 px-3 text-xs font-semibold text-custom-text-100">
|
<h2 className="mt-4 mb-2 px-3 text-xs font-semibold text-custom-text-100">Labels</h2>
|
||||||
Labels
|
|
||||||
</h2>
|
|
||||||
)}
|
)}
|
||||||
<ul className="text-sm text-gray-700">
|
<ul className="text-sm text-gray-700">
|
||||||
{filteredLabels.map((label) => {
|
{filteredLabels.map((label) => {
|
||||||
|
@ -7,17 +7,11 @@ import { mutate } from "swr";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Disclosure, Transition } from "@headlessui/react";
|
import { Disclosure, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu } from "components/ui";
|
import { CustomMenu } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
import {
|
import { ChevronDownIcon, XMarkIcon, PlusIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||||
ChevronDownIcon,
|
|
||||||
XMarkIcon,
|
|
||||||
PlusIcon,
|
|
||||||
PencilIcon,
|
|
||||||
TrashIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
import { Component, X } from "lucide-react";
|
import { Component, X } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IIssueLabels } from "types";
|
import { ICurrentUserResponse, IIssueLabels } from "types";
|
||||||
@ -110,9 +104,7 @@ export const SingleLabelGroup: React.FC<Props> = ({
|
|||||||
<Disclosure.Button>
|
<Disclosure.Button>
|
||||||
<span>
|
<span>
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
className={`h-4 w-4 text-custom-sidebar-text-400 ${
|
className={`h-4 w-4 text-custom-sidebar-text-400 ${!open ? "rotate-90 transform" : ""}`}
|
||||||
!open ? "rotate-90 transform" : ""
|
|
||||||
}`}
|
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
@ -138,8 +130,7 @@ export const SingleLabelGroup: React.FC<Props> = ({
|
|||||||
<span
|
<span
|
||||||
className="h-3.5 w-3.5 flex-shrink-0 rounded-full"
|
className="h-3.5 w-3.5 flex-shrink-0 rounded-full"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor:
|
backgroundColor: child.color && child.color !== "" ? child.color : "#000000",
|
||||||
child.color && child.color !== "" ? child.color : "#000000",
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{child.name}
|
{child.name}
|
||||||
@ -169,10 +160,7 @@ export const SingleLabelGroup: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<button
|
<button className="flex items-center justify-start gap-2" onClick={handleLabelDelete}>
|
||||||
className="flex items-center justify-start gap-2"
|
|
||||||
onClick={handleLabelDelete}
|
|
||||||
>
|
|
||||||
<X className="h-[18px] w-[18px] text-custom-sidebar-text-400 flex-shrink-0" />
|
<X className="h-[18px] w-[18px] text-custom-sidebar-text-400 flex-shrink-0" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@ import { PaperAirplaneIcon } from "@heroicons/react/24/outline";
|
|||||||
// react-hook-form
|
// react-hook-form
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
// services
|
// services
|
||||||
import pagesService from "services/pages.service";
|
import pagesService from "services/page.service";
|
||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
@ -81,8 +81,7 @@ export const CreateBlock: React.FC<Props> = ({ user }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e: any) => {
|
const handleKeyDown = (e: any) => {
|
||||||
const keyCombination =
|
const keyCombination = ((e.ctrlKey || e.metaKey) && e.key === "Enter") || (e.shiftKey && e.key === "Enter");
|
||||||
((e.ctrlKey || e.metaKey) && e.key === "Enter") || (e.shiftKey && e.key === "Enter");
|
|
||||||
|
|
||||||
if (e.key === "Enter" && !keyCombination) {
|
if (e.key === "Enter" && !keyCombination) {
|
||||||
if (watch("name") && watch("name") !== "") {
|
if (watch("name") && watch("name") !== "") {
|
||||||
|
@ -4,8 +4,8 @@ import { mutate } from "swr";
|
|||||||
import { SparklesIcon } from "@heroicons/react/24/outline";
|
import { SparklesIcon } from "@heroicons/react/24/outline";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
// services
|
// services
|
||||||
import pagesService from "services/pages.service";
|
import pagesService from "services/page.service";
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
import aiService from "services/ai.service";
|
import aiService from "services/ai.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
@ -195,8 +195,7 @@ export const CreateUpdateBlockInline: React.FC<Props> = ({
|
|||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message:
|
message: "You have reached the maximum number of requests of 50 requests per month per user.",
|
||||||
"You have reached the maximum number of requests of 50 requests per month per user.",
|
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
@ -294,9 +293,7 @@ export const CreateUpdateBlockInline: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
else if (!value || !watch("description_html"))
|
else if (!value || !watch("description_html"))
|
||||||
return (
|
return <div className="h-32 w-full flex items-center justify-center text-custom-text-200 text-sm" />;
|
||||||
<div className="h-32 w-full flex items-center justify-center text-custom-text-200 text-sm" />
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TipTapEditor
|
<TipTapEditor
|
||||||
@ -351,13 +348,7 @@ export const CreateUpdateBlockInline: React.FC<Props> = ({
|
|||||||
<div className="flex items-center justify-end gap-2 p-4">
|
<div className="flex items-center justify-end gap-2 p-4">
|
||||||
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
|
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
|
||||||
<PrimaryButton type="submit" disabled={watch("name") === ""} loading={isSubmitting}>
|
<PrimaryButton type="submit" disabled={watch("name") === ""} loading={isSubmitting}>
|
||||||
{data
|
{data ? (isSubmitting ? "Updating..." : "Update block") : isSubmitting ? "Adding..." : "Add block"}
|
||||||
? isSubmitting
|
|
||||||
? "Updating..."
|
|
||||||
: "Update block"
|
|
||||||
: isSubmitting
|
|
||||||
? "Adding..."
|
|
||||||
: "Add block"}
|
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -371,9 +362,7 @@ export const CreateUpdateBlockInline: React.FC<Props> = ({
|
|||||||
onResponse={(response) => {
|
onResponse={(response) => {
|
||||||
if (data && handleAiAssistance) {
|
if (data && handleAiAssistance) {
|
||||||
handleAiAssistance(response);
|
handleAiAssistance(response);
|
||||||
editorRef.current?.setEditorValue(
|
editorRef.current?.setEditorValue(`${watch("description_html")}<p>${response}</p>` ?? "");
|
||||||
`${watch("description_html")}<p>${response}</p>` ?? ""
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
setValue("description", {});
|
setValue("description", {});
|
||||||
setValue("description_html", `${watch("description_html")}<p>${response}</p>`);
|
setValue("description_html", `${watch("description_html")}<p>${response}</p>`);
|
||||||
|
@ -7,7 +7,7 @@ import { mutate } from "swr";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import pagesService from "services/pages.service";
|
import pagesService from "services/page.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
// components
|
||||||
@ -15,12 +15,7 @@ import { PageForm } from "./page-form";
|
|||||||
// types
|
// types
|
||||||
import { ICurrentUserResponse, IPage } from "types";
|
import { ICurrentUserResponse, IPage } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import { ALL_PAGES_LIST, FAVORITE_PAGES_LIST, MY_PAGES_LIST, RECENT_PAGES_LIST } from "constants/fetch-keys";
|
||||||
ALL_PAGES_LIST,
|
|
||||||
FAVORITE_PAGES_LIST,
|
|
||||||
MY_PAGES_LIST,
|
|
||||||
RECENT_PAGES_LIST,
|
|
||||||
} from "constants/fetch-keys";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
@ -7,7 +7,7 @@ import { mutate } from "swr";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import pagesService from "services/pages.service";
|
import pagesService from "services/page.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
@ -17,12 +17,7 @@ import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
|||||||
// types
|
// types
|
||||||
import type { ICurrentUserResponse, IPage } from "types";
|
import type { ICurrentUserResponse, IPage } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import { ALL_PAGES_LIST, FAVORITE_PAGES_LIST, MY_PAGES_LIST, RECENT_PAGES_LIST } from "constants/fetch-keys";
|
||||||
ALL_PAGES_LIST,
|
|
||||||
FAVORITE_PAGES_LIST,
|
|
||||||
MY_PAGES_LIST,
|
|
||||||
RECENT_PAGES_LIST,
|
|
||||||
} from "constants/fetch-keys";
|
|
||||||
|
|
||||||
type TConfirmPageDeletionProps = {
|
type TConfirmPageDeletionProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -31,12 +26,7 @@ type TConfirmPageDeletionProps = {
|
|||||||
user: ICurrentUserResponse | undefined;
|
user: ICurrentUserResponse | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = ({
|
export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = ({ isOpen, setIsOpen, data, user }) => {
|
||||||
isOpen,
|
|
||||||
setIsOpen,
|
|
||||||
data,
|
|
||||||
user,
|
|
||||||
}) => {
|
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -121,26 +111,17 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = ({
|
|||||||
<div className="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
<div className="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
<div className="sm:flex sm:items-start">
|
<div className="sm:flex sm:items-start">
|
||||||
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-500/20 sm:mx-0 sm:h-10 sm:w-10">
|
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-500/20 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
<ExclamationTriangleIcon
|
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
|
||||||
className="h-6 w-6 text-red-600"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
<Dialog.Title
|
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
|
||||||
as="h3"
|
|
||||||
className="text-lg font-medium leading-6 text-custom-text-100"
|
|
||||||
>
|
|
||||||
Delete Page
|
Delete Page
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-sm text-custom-text-200">
|
<p className="text-sm text-custom-text-200">
|
||||||
Are you sure you want to delete Page-{" "}
|
Are you sure you want to delete Page-{" "}
|
||||||
<span className="break-words font-medium text-custom-text-100">
|
<span className="break-words font-medium text-custom-text-100">{data?.name}</span>? All of the
|
||||||
{data?.name}
|
data related to the page will be permanently removed. This action cannot be undone.
|
||||||
</span>
|
|
||||||
? All of the data related to the page will be permanently removed. This
|
|
||||||
action cannot be undone.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import pagesService from "services/pages.service";
|
import pagesService from "services/page.service";
|
||||||
// components
|
// components
|
||||||
import { PagesView } from "components/pages";
|
import { PagesView } from "components/pages";
|
||||||
// types
|
// types
|
||||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import pagesService from "services/pages.service";
|
import pagesService from "services/page.service";
|
||||||
// components
|
// components
|
||||||
import { PagesView } from "components/pages";
|
import { PagesView } from "components/pages";
|
||||||
// types
|
// types
|
||||||
@ -18,8 +18,7 @@ export const FavoritePagesList: React.FC<TPagesListProps> = ({ viewType }) => {
|
|||||||
const { data: pages } = useSWR(
|
const { data: pages } = useSWR(
|
||||||
workspaceSlug && projectId ? FAVORITE_PAGES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? FAVORITE_PAGES_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () =>
|
? () => pagesService.getPagesWithParams(workspaceSlug as string, projectId as string, "favorite")
|
||||||
pagesService.getPagesWithParams(workspaceSlug as string, projectId as string, "favorite")
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import pagesService from "services/pages.service";
|
import pagesService from "services/page.service";
|
||||||
// components
|
// components
|
||||||
import { PagesView } from "components/pages";
|
import { PagesView } from "components/pages";
|
||||||
// types
|
// types
|
||||||
@ -18,12 +18,7 @@ export const MyPagesList: React.FC<TPagesListProps> = ({ viewType }) => {
|
|||||||
const { data: pages } = useSWR(
|
const { data: pages } = useSWR(
|
||||||
workspaceSlug && projectId ? MY_PAGES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? MY_PAGES_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () =>
|
? () => pagesService.getPagesWithParams(workspaceSlug as string, projectId as string, "created_by_me")
|
||||||
pagesService.getPagesWithParams(
|
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
"created_by_me"
|
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import pagesService from "services/pages.service";
|
import pagesService from "services/page.service";
|
||||||
// components
|
// components
|
||||||
import { PagesView } from "components/pages";
|
import { PagesView } from "components/pages";
|
||||||
// types
|
// types
|
||||||
@ -18,12 +18,7 @@ export const OtherPagesList: React.FC<TPagesListProps> = ({ viewType }) => {
|
|||||||
const { data: pages } = useSWR(
|
const { data: pages } = useSWR(
|
||||||
workspaceSlug && projectId ? OTHER_PAGES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? OTHER_PAGES_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () =>
|
? () => pagesService.getPagesWithParams(workspaceSlug as string, projectId as string, "created_by_other")
|
||||||
pagesService.getPagesWithParams(
|
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
"created_by_other"
|
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import pagesService from "services/pages.service";
|
import pagesService from "services/page.service";
|
||||||
// components
|
// components
|
||||||
import { PagesView } from "components/pages";
|
import { PagesView } from "components/pages";
|
||||||
// ui
|
// ui
|
||||||
@ -28,9 +28,7 @@ export const RecentPagesList: React.FC<TPagesListProps> = ({ viewType }) => {
|
|||||||
|
|
||||||
const { data: pages } = useSWR(
|
const { data: pages } = useSWR(
|
||||||
workspaceSlug && projectId ? RECENT_PAGES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? RECENT_PAGES_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? () => pagesService.getRecentPages(workspaceSlug as string, projectId as string) : null
|
||||||
? () => pagesService.getRecentPages(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const isEmpty = pages && Object.keys(pages).every((key) => pages[key].length === 0);
|
const isEmpty = pages && Object.keys(pages).every((key) => pages[key].length === 0);
|
||||||
@ -44,9 +42,7 @@ export const RecentPagesList: React.FC<TPagesListProps> = ({ viewType }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={key} className="h-full overflow-hidden">
|
<div key={key} className="h-full overflow-hidden">
|
||||||
<h2 className="text-xl font-semibold capitalize mb-2">
|
<h2 className="text-xl font-semibold capitalize mb-2">{replaceUnderscoreIfSnakeCase(key)}</h2>
|
||||||
{replaceUnderscoreIfSnakeCase(key)}
|
|
||||||
</h2>
|
|
||||||
<PagesView pages={pages[key as keyof RecentPagesResponse]} viewType={viewType} />
|
<PagesView pages={pages[key as keyof RecentPagesResponse]} viewType={viewType} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -4,18 +4,13 @@ import useSWR, { mutate } from "swr";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import pagesService from "services/pages.service";
|
import pagesService from "services/page.service";
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useUserAuth from "hooks/use-user-auth";
|
import useUserAuth from "hooks/use-user-auth";
|
||||||
// components
|
// components
|
||||||
import {
|
import { CreateUpdatePageModal, DeletePageModal, SinglePageDetailedItem, SinglePageListItem } from "components/pages";
|
||||||
CreateUpdatePageModal,
|
|
||||||
DeletePageModal,
|
|
||||||
SinglePageDetailedItem,
|
|
||||||
SinglePageListItem,
|
|
||||||
} from "components/pages";
|
|
||||||
// ui
|
// ui
|
||||||
import { EmptyState, Loader } from "components/ui";
|
import { EmptyState, Loader } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
@ -91,11 +86,7 @@ export const PagesView: React.FC<Props> = ({ pages, viewType }) => {
|
|||||||
}),
|
}),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
mutate<IPage[]>(
|
mutate<IPage[]>(FAVORITE_PAGES_LIST(projectId.toString()), (prevData) => [page, ...(prevData ?? [])], false);
|
||||||
FAVORITE_PAGES_LIST(projectId.toString()),
|
|
||||||
(prevData) => [page, ...(prevData ?? [])],
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
pagesService
|
pagesService
|
||||||
.addPageToFavorites(workspaceSlug.toString(), projectId.toString(), {
|
.addPageToFavorites(workspaceSlug.toString(), projectId.toString(), {
|
||||||
@ -185,11 +176,9 @@ export const PagesView: React.FC<Props> = ({ pages, viewType }) => {
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
pagesService
|
pagesService.patchPage(workspaceSlug.toString(), projectId.toString(), page.id, formData, user).then(() => {
|
||||||
.patchPage(workspaceSlug.toString(), projectId.toString(), page.id, formData, user)
|
mutate(RECENT_PAGES_LIST(projectId.toString()));
|
||||||
.then(() => {
|
});
|
||||||
mutate(RECENT_PAGES_LIST(projectId.toString()));
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -10,8 +10,8 @@ import { useForm } from "react-hook-form";
|
|||||||
// react-beautiful-dnd
|
// react-beautiful-dnd
|
||||||
import { Draggable } from "react-beautiful-dnd";
|
import { Draggable } from "react-beautiful-dnd";
|
||||||
// services
|
// services
|
||||||
import pagesService from "services/pages.service";
|
import pagesService from "services/page.service";
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
import aiService from "services/ai.service";
|
import aiService from "services/ai.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
@ -48,13 +48,7 @@ type Props = {
|
|||||||
user: ICurrentUserResponse | undefined;
|
user: ICurrentUserResponse | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SinglePageBlock: React.FC<Props> = ({
|
export const SinglePageBlock: React.FC<Props> = ({ block, projectDetails, showBlockDetails, index, user }) => {
|
||||||
block,
|
|
||||||
projectDetails,
|
|
||||||
showBlockDetails,
|
|
||||||
index,
|
|
||||||
user,
|
|
||||||
}) => {
|
|
||||||
const [isSyncing, setIsSyncing] = useState(false);
|
const [isSyncing, setIsSyncing] = useState(false);
|
||||||
const [createBlockForm, setCreateBlockForm] = useState(false);
|
const [createBlockForm, setCreateBlockForm] = useState(false);
|
||||||
const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false);
|
const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false);
|
||||||
@ -131,13 +125,7 @@ export const SinglePageBlock: React.FC<Props> = ({
|
|||||||
if (!workspaceSlug || !projectId || !pageId) return;
|
if (!workspaceSlug || !projectId || !pageId) return;
|
||||||
|
|
||||||
await pagesService
|
await pagesService
|
||||||
.convertPageBlockToIssue(
|
.convertPageBlockToIssue(workspaceSlug as string, projectId as string, pageId as string, block.id, user)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
pageId as string,
|
|
||||||
block.id,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then((res: IIssue) => {
|
.then((res: IIssue) => {
|
||||||
mutate<IPageBlock[]>(
|
mutate<IPageBlock[]>(
|
||||||
PAGE_BLOCKS_LIST(pageId as string),
|
PAGE_BLOCKS_LIST(pageId as string),
|
||||||
@ -175,13 +163,7 @@ export const SinglePageBlock: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
await pagesService
|
await pagesService
|
||||||
.deletePageBlock(
|
.deletePageBlock(workspaceSlug as string, projectId as string, pageId as string, block.id, user)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
pageId as string,
|
|
||||||
block.id,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
@ -221,8 +203,7 @@ export const SinglePageBlock: React.FC<Props> = ({
|
|||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message:
|
message: "You have reached the maximum number of requests of 50 requests per month per user.",
|
||||||
"You have reached the maximum number of requests of 50 requests per month per user.",
|
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
@ -283,12 +264,9 @@ export const SinglePageBlock: React.FC<Props> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCopyText = () => {
|
const handleCopyText = () => {
|
||||||
const originURL =
|
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
||||||
typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
|
|
||||||
|
|
||||||
copyTextToClipboard(
|
copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${block.issue}`).then(() => {
|
||||||
`${originURL}/${workspaceSlug}/projects/${projectId}/issues/${block.issue}`
|
|
||||||
).then(() => {
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Link Copied!",
|
title: "Link Copied!",
|
||||||
@ -343,11 +321,7 @@ export const SinglePageBlock: React.FC<Props> = ({
|
|||||||
>
|
>
|
||||||
{block.issue && block.sync && (
|
{block.issue && block.sync && (
|
||||||
<div className="flex flex-shrink-0 cursor-default items-center gap-1 rounded py-1 px-1.5 text-xs">
|
<div className="flex flex-shrink-0 cursor-default items-center gap-1 rounded py-1 px-1.5 text-xs">
|
||||||
{isSyncing ? (
|
{isSyncing ? <ArrowPathIcon className="h-3 w-3 animate-spin" /> : <CheckIcon className="h-3 w-3" />}
|
||||||
<ArrowPathIcon className="h-3 w-3 animate-spin" />
|
|
||||||
) : (
|
|
||||||
<CheckIcon className="h-3 w-3" />
|
|
||||||
)}
|
|
||||||
{isSyncing ? "Syncing..." : "Synced"}
|
{isSyncing ? "Syncing..." : "Synced"}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -431,9 +405,7 @@ export const SinglePageBlock: React.FC<Props> = ({
|
|||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{block.issue && (
|
{block.issue && (
|
||||||
<div className="mr-1.5 flex">
|
<div className="mr-1.5 flex">
|
||||||
<Link
|
<Link href={`/${workspaceSlug}/projects/${projectId}/issues/${block.issue}`}>
|
||||||
href={`/${workspaceSlug}/projects/${projectId}/issues/${block.issue}`}
|
|
||||||
>
|
|
||||||
<a className="flex h-6 flex-shrink-0 items-center gap-1 rounded bg-custom-background-80 px-1.5 py-1 text-xs">
|
<a className="flex h-6 flex-shrink-0 items-center gap-1 rounded bg-custom-background-80 px-1.5 py-1 text-xs">
|
||||||
<LayerDiagonalIcon height="16" width="16" />
|
<LayerDiagonalIcon height="16" width="16" />
|
||||||
{projectDetails?.identifier}-{block.issue_detail?.sequence_id}
|
{projectDetails?.identifier}-{block.issue_detail?.sequence_id}
|
||||||
|
@ -7,7 +7,7 @@ import useSWR from "swr";
|
|||||||
// react-beautiful-dnd
|
// react-beautiful-dnd
|
||||||
import { DropResult } from "react-beautiful-dnd";
|
import { DropResult } from "react-beautiful-dnd";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
import userService from "services/user.service";
|
import userService from "services/user.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useProfileIssues from "hooks/use-profile-issues";
|
import useProfileIssues from "hooks/use-profile-issues";
|
||||||
@ -31,9 +31,7 @@ export const ProfileIssuesView = () => {
|
|||||||
|
|
||||||
// update issue modal
|
// update issue modal
|
||||||
const [editIssueModal, setEditIssueModal] = useState(false);
|
const [editIssueModal, setEditIssueModal] = useState(false);
|
||||||
const [issueToEdit, setIssueToEdit] = useState<
|
const [issueToEdit, setIssueToEdit] = useState<(IIssue & { actionType: "edit" | "delete" }) | undefined>(undefined);
|
||||||
(IIssue & { actionType: "edit" | "delete" }) | undefined
|
|
||||||
>(undefined);
|
|
||||||
|
|
||||||
// delete issue modal
|
// delete issue modal
|
||||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||||
@ -60,19 +58,14 @@ export const ProfileIssuesView = () => {
|
|||||||
} = useProfileIssues(workspaceSlug?.toString(), userId?.toString());
|
} = useProfileIssues(workspaceSlug?.toString(), userId?.toString());
|
||||||
|
|
||||||
const { data: profileData } = useSWR(
|
const { data: profileData } = useSWR(
|
||||||
|
workspaceSlug && userId ? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString()) : null,
|
||||||
workspaceSlug && userId
|
workspaceSlug && userId
|
||||||
? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString())
|
? () => userService.getUserProfileProjectsSegregation(workspaceSlug.toString(), userId.toString())
|
||||||
: null,
|
|
||||||
workspaceSlug && userId
|
|
||||||
? () =>
|
|
||||||
userService.getUserProfileProjectsSegregation(workspaceSlug.toString(), userId.toString())
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: labels } = useSWR(
|
const { data: labels } = useSWR(
|
||||||
workspaceSlug && (filters?.labels ?? []).length > 0
|
workspaceSlug && (filters?.labels ?? []).length > 0 ? WORKSPACE_LABELS(workspaceSlug.toString()) : null,
|
||||||
? WORKSPACE_LABELS(workspaceSlug.toString())
|
|
||||||
: null,
|
|
||||||
workspaceSlug && (filters?.labels ?? []).length > 0
|
workspaceSlug && (filters?.labels ?? []).length > 0
|
||||||
? () => issuesService.getWorkspaceLabels(workspaceSlug.toString())
|
? () => issuesService.getWorkspaceLabels(workspaceSlug.toString())
|
||||||
: null
|
: null
|
||||||
@ -90,13 +83,7 @@ export const ProfileIssuesView = () => {
|
|||||||
async (result: DropResult) => {
|
async (result: DropResult) => {
|
||||||
setTrashBox(false);
|
setTrashBox(false);
|
||||||
|
|
||||||
if (
|
if (!result.destination || !workspaceSlug || !groupedIssues || displayFilters?.group_by !== "priority") return;
|
||||||
!result.destination ||
|
|
||||||
!workspaceSlug ||
|
|
||||||
!groupedIssues ||
|
|
||||||
displayFilters?.group_by !== "priority"
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const { source, destination } = result;
|
const { source, destination } = result;
|
||||||
|
|
||||||
@ -125,10 +112,7 @@ export const ProfileIssuesView = () => {
|
|||||||
return {
|
return {
|
||||||
...prevData,
|
...prevData,
|
||||||
[sourceGroup]: orderArrayBy(sourceGroupArray, displayFilters.order_by ?? "-created_at"),
|
[sourceGroup]: orderArrayBy(sourceGroupArray, displayFilters.order_by ?? "-created_at"),
|
||||||
[destinationGroup]: orderArrayBy(
|
[destinationGroup]: orderArrayBy(destinationGroupArray, displayFilters.order_by ?? "-created_at"),
|
||||||
destinationGroupArray,
|
|
||||||
displayFilters.order_by ?? "-created_at"
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
@ -218,15 +202,11 @@ export const ProfileIssuesView = () => {
|
|||||||
(key) => filtersToDisplay[key as keyof IIssueFilterOptions] === null
|
(key) => filtersToDisplay[key as keyof IIssueFilterOptions] === null
|
||||||
);
|
);
|
||||||
const areFiltersApplied =
|
const areFiltersApplied =
|
||||||
Object.keys(filtersToDisplay).length > 0 &&
|
Object.keys(filtersToDisplay).length > 0 && nullFilters.length !== Object.keys(filtersToDisplay).length;
|
||||||
nullFilters.length !== Object.keys(filtersToDisplay).length;
|
|
||||||
|
|
||||||
const isSubscribedIssuesRoute = router.pathname.includes("subscribed");
|
const isSubscribedIssuesRoute = router.pathname.includes("subscribed");
|
||||||
const isMySubscribedIssues =
|
const isMySubscribedIssues =
|
||||||
(filters.subscriber &&
|
(filters.subscriber && filters.subscriber.length > 0 && router.pathname.includes("my-issues")) ?? false;
|
||||||
filters.subscriber.length > 0 &&
|
|
||||||
router.pathname.includes("my-issues")) ??
|
|
||||||
false;
|
|
||||||
|
|
||||||
const disableAddIssueOption = isSubscribedIssuesRoute || isMySubscribedIssues;
|
const disableAddIssueOption = isSubscribedIssuesRoute || isMySubscribedIssues;
|
||||||
|
|
||||||
|
@ -6,21 +6,14 @@ import { Controller, useForm } from "react-hook-form";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// ui components
|
// ui components
|
||||||
import {
|
import { ToggleSwitch, PrimaryButton, SecondaryButton, Icon, DangerButton, Loader } from "components/ui";
|
||||||
ToggleSwitch,
|
|
||||||
PrimaryButton,
|
|
||||||
SecondaryButton,
|
|
||||||
Icon,
|
|
||||||
DangerButton,
|
|
||||||
Loader,
|
|
||||||
} from "components/ui";
|
|
||||||
import { CustomPopover } from "./popover";
|
import { CustomPopover } from "./popover";
|
||||||
// mobx react lite
|
// mobx react lite
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// mobx store
|
// mobx store
|
||||||
import { useMobxStore } from "lib/mobx/store-provider";
|
import { useMobxStore } from "lib/mobx/store-provider";
|
||||||
import { RootStore } from "store/root";
|
import { RootStore } from "store/root";
|
||||||
import { IProjectPublishSettings, TProjectPublishViews } from "store/project-publish";
|
import { IProjectPublishSettings, TProjectPublishViews } from "store/project_publish";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
import useProjectDetails from "hooks/use-project-details";
|
||||||
@ -65,8 +58,8 @@ export const PublishProjectModal: React.FC<Props> = observer(() => {
|
|||||||
|
|
||||||
let plane_deploy_url = process.env.NEXT_PUBLIC_DEPLOY_URL;
|
let plane_deploy_url = process.env.NEXT_PUBLIC_DEPLOY_URL;
|
||||||
|
|
||||||
if (typeof window !== 'undefined' && !plane_deploy_url) {
|
if (typeof window !== "undefined" && !plane_deploy_url) {
|
||||||
plane_deploy_url= window.location.protocol + "//" + window.location.host + "/spaces";
|
plane_deploy_url = window.location.protocol + "//" + window.location.host + "/spaces";
|
||||||
}
|
}
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -101,10 +94,7 @@ export const PublishProjectModal: React.FC<Props> = observer(() => {
|
|||||||
|
|
||||||
// prefill form with the saved settings if the project is already published
|
// prefill form with the saved settings if the project is already published
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (projectPublish.projectPublishSettings && projectPublish.projectPublishSettings !== "not-initialized") {
|
||||||
projectPublish.projectPublishSettings &&
|
|
||||||
projectPublish.projectPublishSettings !== "not-initialized"
|
|
||||||
) {
|
|
||||||
let userBoards: TProjectPublishViews[] = [];
|
let userBoards: TProjectPublishViews[] = [];
|
||||||
|
|
||||||
if (projectPublish.projectPublishSettings?.views) {
|
if (projectPublish.projectPublishSettings?.views) {
|
||||||
@ -143,11 +133,7 @@ export const PublishProjectModal: React.FC<Props> = observer(() => {
|
|||||||
projectPublish.project_id !== null &&
|
projectPublish.project_id !== null &&
|
||||||
projectPublish?.projectPublishSettings === "not-initialized"
|
projectPublish?.projectPublishSettings === "not-initialized"
|
||||||
) {
|
) {
|
||||||
projectPublish.getProjectSettingsAsync(
|
projectPublish.getProjectSettingsAsync(workspaceSlug.toString(), projectPublish.project_id, null);
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectPublish.project_id,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}, [workspaceSlug, projectPublish, projectPublish.projectPublishModal]);
|
}, [workspaceSlug, projectPublish, projectPublish.projectPublishModal]);
|
||||||
|
|
||||||
@ -202,12 +188,7 @@ export const PublishProjectModal: React.FC<Props> = observer(() => {
|
|||||||
setIsUnpublishing(true);
|
setIsUnpublishing(true);
|
||||||
|
|
||||||
projectPublish
|
projectPublish
|
||||||
.unPublishProject(
|
.unPublishProject(workspaceSlug.toString(), projectPublish.project_id as string, publishId, null)
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectPublish.project_id as string,
|
|
||||||
publishId,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
mutateProjectDetails();
|
mutateProjectDetails();
|
||||||
|
|
||||||
@ -269,11 +250,7 @@ export const PublishProjectModal: React.FC<Props> = observer(() => {
|
|||||||
|
|
||||||
// check if an update is required or not
|
// check if an update is required or not
|
||||||
const checkIfUpdateIsRequired = () => {
|
const checkIfUpdateIsRequired = () => {
|
||||||
if (
|
if (!projectPublish.projectPublishSettings || projectPublish.projectPublishSettings === "not-initialized") return;
|
||||||
!projectPublish.projectPublishSettings ||
|
|
||||||
projectPublish.projectPublishSettings === "not-initialized"
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const currentSettings = projectPublish.projectPublishSettings as IProjectPublishSettings;
|
const currentSettings = projectPublish.projectPublishSettings as IProjectPublishSettings;
|
||||||
const newSettings = getValues();
|
const newSettings = getValues();
|
||||||
@ -289,8 +266,7 @@ export const PublishProjectModal: React.FC<Props> = observer(() => {
|
|||||||
|
|
||||||
let viewCheckFlag = 0;
|
let viewCheckFlag = 0;
|
||||||
viewOptions.forEach((option) => {
|
viewOptions.forEach((option) => {
|
||||||
if (currentSettings.views[option.key] !== newSettings.views.includes(option.key))
|
if (currentSettings.views[option.key] !== newSettings.views.includes(option.key)) viewCheckFlag++;
|
||||||
viewCheckFlag++;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (viewCheckFlag !== 0) {
|
if (viewCheckFlag !== 0) {
|
||||||
@ -416,9 +392,7 @@ export const PublishProjectModal: React.FC<Props> = observer(() => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="text-sm">{option.label}</div>
|
<div className="text-sm">{option.label}</div>
|
||||||
<div
|
<div className={`w-[18px] h-[18px] relative flex justify-center items-center`}>
|
||||||
className={`w-[18px] h-[18px] relative flex justify-center items-center`}
|
|
||||||
>
|
|
||||||
{value.length > 0 && value.includes(option.key) && (
|
{value.length > 0 && value.includes(option.key) && (
|
||||||
<Icon iconName="done" className="!text-lg" />
|
<Icon iconName="done" className="!text-lg" />
|
||||||
)}
|
)}
|
||||||
|
@ -11,7 +11,7 @@ import { TwitterPicker } from "react-color";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Dialog, Popover, Transition } from "@headlessui/react";
|
import { Dialog, Popover, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import stateService from "services/state.service";
|
import stateService from "services/project_state.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
@ -131,10 +131,7 @@ export const CreateStateModal: React.FC<Props> = ({ isOpen, projectId, handleClo
|
|||||||
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-80 px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
|
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-80 px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div>
|
<div>
|
||||||
<Dialog.Title
|
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
|
||||||
as="h3"
|
|
||||||
className="text-lg font-medium leading-6 text-custom-text-100"
|
|
||||||
>
|
|
||||||
Create State
|
Create State
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2 space-y-3">
|
<div className="mt-2 space-y-3">
|
||||||
@ -215,10 +212,7 @@ export const CreateStateModal: React.FC<Props> = ({ isOpen, projectId, handleClo
|
|||||||
name="color"
|
name="color"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<TwitterPicker
|
<TwitterPicker color={value} onChange={(value) => onChange(value.hex)} />
|
||||||
color={value}
|
|
||||||
onChange={(value) => onChange(value.hex)}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Popover.Panel>
|
</Popover.Panel>
|
||||||
|
@ -11,7 +11,7 @@ import { TwitterPicker } from "react-color";
|
|||||||
// headless ui
|
// headless ui
|
||||||
import { Popover, Transition } from "@headlessui/react";
|
import { Popover, Transition } from "@headlessui/react";
|
||||||
// services
|
// services
|
||||||
import stateService from "services/state.service";
|
import stateService from "services/project_state.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
@ -39,13 +39,7 @@ const defaultValues: Partial<IState> = {
|
|||||||
group: "backlog",
|
group: "backlog",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateUpdateStateInline: React.FC<Props> = ({
|
export const CreateUpdateStateInline: React.FC<Props> = ({ data, onClose, selectedGroup, user, groupLength }) => {
|
||||||
data,
|
|
||||||
onClose,
|
|
||||||
selectedGroup,
|
|
||||||
user,
|
|
||||||
groupLength,
|
|
||||||
}) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
@ -153,8 +147,7 @@ export const CreateUpdateStateInline: React.FC<Props> = ({
|
|||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message:
|
message: "Another state exists with the same name. Please try again with another name.",
|
||||||
"Another state exists with the same name. Please try again with another name.",
|
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
@ -230,9 +223,7 @@ export const CreateUpdateStateInline: React.FC<Props> = ({
|
|||||||
name="group"
|
name="group"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<Tooltip
|
<Tooltip tooltipContent={groupLength === 1 ? "Cannot have an empty group." : "Choose State"}>
|
||||||
tooltipContent={groupLength === 1 ? "Cannot have an empty group." : "Choose State"}
|
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
disabled={groupLength === 1}
|
disabled={groupLength === 1}
|
||||||
|
@ -9,7 +9,7 @@ import { Dialog, Transition } from "@headlessui/react";
|
|||||||
// icons
|
// icons
|
||||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||||
// services
|
// services
|
||||||
import stateServices from "services/state.service";
|
import stateServices from "services/project_state.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
@ -112,24 +112,17 @@ export const DeleteStateModal: React.FC<Props> = ({ isOpen, onClose, data, user
|
|||||||
<div className="bg-custom-background-80 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
<div className="bg-custom-background-80 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
<div className="sm:flex sm:items-start">
|
<div className="sm:flex sm:items-start">
|
||||||
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
<ExclamationTriangleIcon
|
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
|
||||||
className="h-6 w-6 text-red-600"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
<Dialog.Title
|
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
|
||||||
as="h3"
|
|
||||||
className="text-lg font-medium leading-6 text-custom-text-100"
|
|
||||||
>
|
|
||||||
Delete State
|
Delete State
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p className="text-sm text-custom-text-200">
|
<p className="text-sm text-custom-text-200">
|
||||||
Are you sure you want to delete state-{" "}
|
Are you sure you want to delete state-{" "}
|
||||||
<span className="font-medium text-custom-text-100">{data?.name}</span>?
|
<span className="font-medium text-custom-text-100">{data?.name}</span>? All of the data
|
||||||
All of the data related to the state will be permanently removed. This
|
related to the state will be permanently removed. This action cannot be undone.
|
||||||
action cannot be undone.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,7 +5,7 @@ import { useRouter } from "next/router";
|
|||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import stateService from "services/state.service";
|
import stateService from "services/project_state.service";
|
||||||
// ui
|
// ui
|
||||||
import { Tooltip } from "components/ui";
|
import { Tooltip } from "components/ui";
|
||||||
// icons
|
// icons
|
||||||
@ -58,11 +58,7 @@ export const SingleState: React.FC<Props> = ({
|
|||||||
}));
|
}));
|
||||||
newStatesList = orderArrayBy(newStatesList, "sequence", "ascending");
|
newStatesList = orderArrayBy(newStatesList, "sequence", "ascending");
|
||||||
|
|
||||||
mutate(
|
mutate(STATES_LIST(projectId as string), orderStateGroups(groupBy(newStatesList, "group")), false);
|
||||||
STATES_LIST(projectId as string),
|
|
||||||
orderStateGroups(groupBy(newStatesList, "group")),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
if (currentDefaultState)
|
if (currentDefaultState)
|
||||||
stateService
|
stateService
|
||||||
@ -131,11 +127,7 @@ export const SingleState: React.FC<Props> = ({
|
|||||||
}));
|
}));
|
||||||
newStatesList = orderArrayBy(newStatesList, "sequence", "ascending");
|
newStatesList = orderArrayBy(newStatesList, "sequence", "ascending");
|
||||||
|
|
||||||
mutate(
|
mutate(STATES_LIST(projectId as string), orderStateGroups(groupBy(newStatesList, "group")), false);
|
||||||
STATES_LIST(projectId as string),
|
|
||||||
orderStateGroups(groupBy(newStatesList, "group")),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
stateService
|
stateService
|
||||||
.patchState(
|
.patchState(
|
||||||
@ -216,26 +208,14 @@ export const SingleState: React.FC<Props> = ({
|
|||||||
>
|
>
|
||||||
{state.default ? (
|
{state.default ? (
|
||||||
<Tooltip tooltipContent="Cannot delete the default state.">
|
<Tooltip tooltipContent="Cannot delete the default state.">
|
||||||
<X
|
<X className={`h-4 w-4 ${groupLength < 1 ? "text-custom-sidebar-text-400" : "text-red-500"}`} />
|
||||||
className={`h-4 w-4 ${
|
|
||||||
groupLength < 1 ? "text-custom-sidebar-text-400" : "text-red-500"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : groupLength === 1 ? (
|
) : groupLength === 1 ? (
|
||||||
<Tooltip tooltipContent="Cannot have an empty group.">
|
<Tooltip tooltipContent="Cannot have an empty group.">
|
||||||
<X
|
<X className={`h-4 w-4 ${groupLength < 1 ? "text-custom-sidebar-text-400" : "text-red-500"}`} />
|
||||||
className={`h-4 w-4 ${
|
|
||||||
groupLength < 1 ? "text-custom-sidebar-text-400" : "text-red-500"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<X
|
<X className={`h-4 w-4 ${groupLength < 1 ? "text-custom-sidebar-text-400" : "text-red-500"}`} />
|
||||||
className={`h-4 w-4 ${
|
|
||||||
groupLength < 1 ? "text-custom-sidebar-text-400" : "text-red-500"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,7 +7,7 @@ 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 stateService from "services/state.service";
|
import stateService from "services/project_state.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useProjectMembers from "hooks/use-project-members";
|
import useProjectMembers from "hooks/use-project-members";
|
||||||
// components
|
// components
|
||||||
@ -20,7 +20,7 @@ import { checkIfArraysHaveSameElements } from "helpers/array.helper";
|
|||||||
import { getStatesList } from "helpers/state.helper";
|
import { getStatesList } from "helpers/state.helper";
|
||||||
// types
|
// types
|
||||||
import { IQuery, IView } from "types";
|
import { IQuery, IView } from "types";
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { PROJECT_ISSUE_LABELS, STATES_LIST } from "constants/fetch-keys";
|
import { PROJECT_ISSUE_LABELS, STATES_LIST } from "constants/fetch-keys";
|
||||||
|
|
||||||
@ -37,13 +37,7 @@ const defaultValues: Partial<IView> = {
|
|||||||
description: "",
|
description: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ViewForm: React.FC<Props> = ({
|
export const ViewForm: React.FC<Props> = ({ handleFormSubmit, handleClose, status, data, preLoadedData }) => {
|
||||||
handleFormSubmit,
|
|
||||||
handleClose,
|
|
||||||
status,
|
|
||||||
data,
|
|
||||||
preLoadedData,
|
|
||||||
}) => {
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId } = router.query;
|
const { workspaceSlug, projectId } = router.query;
|
||||||
|
|
||||||
@ -60,9 +54,7 @@ export const ViewForm: React.FC<Props> = ({
|
|||||||
const filters = watch("query");
|
const filters = watch("query");
|
||||||
|
|
||||||
const { data: stateGroups } = useSWR(
|
const { data: stateGroups } = useSWR(
|
||||||
workspaceSlug && projectId && (filters?.state ?? []).length > 0
|
workspaceSlug && projectId && (filters?.state ?? []).length > 0 ? STATES_LIST(projectId as string) : null,
|
||||||
? STATES_LIST(projectId as string)
|
|
||||||
: null,
|
|
||||||
workspaceSlug && (filters?.state ?? []).length > 0
|
workspaceSlug && (filters?.state ?? []).length > 0
|
||||||
? () => stateService.getStates(workspaceSlug as string, projectId as string)
|
? () => stateService.getStates(workspaceSlug as string, projectId as string)
|
||||||
: null
|
: null
|
||||||
@ -117,9 +109,7 @@ export const ViewForm: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(handleCreateUpdateView)}>
|
<form onSubmit={handleSubmit(handleCreateUpdateView)}>
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<h3 className="text-lg font-medium leading-6 text-custom-text-100">
|
<h3 className="text-lg font-medium leading-6 text-custom-text-100">{status ? "Update" : "Create"} View</h3>
|
||||||
{status ? "Update" : "Create"} View
|
|
||||||
</h3>
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
@ -157,10 +147,7 @@ export const ViewForm: React.FC<Props> = ({
|
|||||||
const key = option.key as keyof typeof filters;
|
const key = option.key as keyof typeof filters;
|
||||||
|
|
||||||
if (key === "start_date" || key === "target_date") {
|
if (key === "start_date" || key === "target_date") {
|
||||||
const valueExists = checkIfArraysHaveSameElements(
|
const valueExists = checkIfArraysHaveSameElements(filters?.[key] ?? [], option.value);
|
||||||
filters?.[key] ?? [],
|
|
||||||
option.value
|
|
||||||
);
|
|
||||||
|
|
||||||
setValue("query", {
|
setValue("query", {
|
||||||
...filters,
|
...filters,
|
||||||
|
@ -5,9 +5,9 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import stateService from "services/state.service";
|
import stateService from "services/project_state.service";
|
||||||
import projectService from "services/project.service";
|
import projectService from "services/project.service";
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// components
|
// components
|
||||||
import { DateFilterModal } from "components/core";
|
import { DateFilterModal } from "components/core";
|
||||||
// ui
|
// ui
|
||||||
@ -32,12 +32,7 @@ type Props = {
|
|||||||
height?: "sm" | "md" | "rg" | "lg";
|
height?: "sm" | "md" | "rg" | "lg";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SelectFilters: React.FC<Props> = ({
|
export const SelectFilters: React.FC<Props> = ({ filters, onSelect, direction = "right", height = "md" }) => {
|
||||||
filters,
|
|
||||||
onSelect,
|
|
||||||
direction = "right",
|
|
||||||
height = "md",
|
|
||||||
}) => {
|
|
||||||
const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false);
|
const [isDateFilterModalOpen, setIsDateFilterModalOpen] = useState(false);
|
||||||
const [dateFilterType, setDateFilterType] = useState<{
|
const [dateFilterType, setDateFilterType] = useState<{
|
||||||
title: string;
|
title: string;
|
||||||
@ -52,9 +47,7 @@ export const SelectFilters: React.FC<Props> = ({
|
|||||||
|
|
||||||
const { data: states } = useSWR(
|
const { data: states } = useSWR(
|
||||||
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? () => stateService.getStates(workspaceSlug as string, projectId as string) : null
|
||||||
? () => stateService.getStates(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
const statesList = getStatesList(states);
|
const statesList = getStatesList(states);
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import { mutate } from "swr";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
|
|
||||||
// fetch keys
|
// fetch keys
|
||||||
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
||||||
@ -75,12 +75,7 @@ export const CreateUpdateLinkForm: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
if (!data)
|
if (!data)
|
||||||
await issuesService
|
await issuesService
|
||||||
.createIssueLink(
|
.createIssueLink(workspaceSlug.toString(), projectId.toString(), issueId.toString(), payload)
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
issueId.toString(),
|
|
||||||
payload
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
onSuccess();
|
onSuccess();
|
||||||
mutate(ISSUE_DETAILS(issueId.toString()));
|
mutate(ISSUE_DETAILS(issueId.toString()));
|
||||||
@ -110,20 +105,10 @@ export const CreateUpdateLinkForm: React.FC<Props> = (props) => {
|
|||||||
: l
|
: l
|
||||||
);
|
);
|
||||||
|
|
||||||
mutate(
|
mutate(ISSUE_DETAILS(issueId.toString()), (prevData) => ({ ...prevData, issue_link: updatedLinks }), false);
|
||||||
ISSUE_DETAILS(issueId.toString()),
|
|
||||||
(prevData) => ({ ...prevData, issue_link: updatedLinks }),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
await issuesService
|
await issuesService
|
||||||
.updateIssueLink(
|
.updateIssueLink(workspaceSlug.toString(), projectId.toString(), issueId.toString(), data!.id, payload)
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
issueId.toString(),
|
|
||||||
data!.id,
|
|
||||||
payload
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
onSuccess();
|
onSuccess();
|
||||||
mutate(ISSUE_DETAILS(issueId.toString()));
|
mutate(ISSUE_DETAILS(issueId.toString()));
|
||||||
@ -172,13 +157,7 @@ export const CreateUpdateLinkForm: React.FC<Props> = (props) => {
|
|||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
className="w-full !py-2 text-custom-text-300 !text-base flex items-center justify-center"
|
className="w-full !py-2 text-custom-text-300 !text-base flex items-center justify-center"
|
||||||
>
|
>
|
||||||
{data
|
{data ? (isSubmitting ? "Updating Link..." : "Update Link") : isSubmitting ? "Adding Link..." : "Add Link"}
|
||||||
? isSubmitting
|
|
||||||
? "Updating Link..."
|
|
||||||
: "Update Link"
|
|
||||||
: isSubmitting
|
|
||||||
? "Adding Link..."
|
|
||||||
: "Add Link"}
|
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -11,7 +11,7 @@ import useSWR, { mutate } from "swr";
|
|||||||
import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
|
import { PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
@ -45,12 +45,7 @@ export const IssueActivity: React.FC<Props> = (props) => {
|
|||||||
const { data: issueActivities, mutate: mutateIssueActivity } = useSWR(
|
const { data: issueActivities, mutate: mutateIssueActivity } = useSWR(
|
||||||
workspaceSlug && projectId && issueId ? PROJECT_ISSUES_ACTIVITY(issueId.toString()) : null,
|
workspaceSlug && projectId && issueId ? PROJECT_ISSUES_ACTIVITY(issueId.toString()) : null,
|
||||||
workspaceSlug && projectId && issueId
|
workspaceSlug && projectId && issueId
|
||||||
? () =>
|
? () => issuesService.getIssueActivities(workspaceSlug.toString(), projectId.toString(), issueId.toString())
|
||||||
issuesService.getIssueActivities(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
issueId.toString()
|
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -58,14 +53,7 @@ export const IssueActivity: React.FC<Props> = (props) => {
|
|||||||
if (!workspaceSlug || !projectId || !issueId) return;
|
if (!workspaceSlug || !projectId || !issueId) return;
|
||||||
|
|
||||||
await issuesService
|
await issuesService
|
||||||
.patchIssueComment(
|
.patchIssueComment(workspaceSlug as string, projectId as string, issueId as string, comment.id, comment, user)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
issueId as string,
|
|
||||||
comment.id,
|
|
||||||
comment,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => mutateIssueActivity());
|
.then(() => mutateIssueActivity());
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,13 +63,7 @@ export const IssueActivity: React.FC<Props> = (props) => {
|
|||||||
mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false);
|
mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false);
|
||||||
|
|
||||||
await issuesService
|
await issuesService
|
||||||
.deleteIssueComment(
|
.deleteIssueComment(workspaceSlug as string, projectId as string, issueId as string, commentId, user)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
issueId as string,
|
|
||||||
commentId,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => mutateIssueActivity());
|
.then(() => mutateIssueActivity());
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,13 +71,7 @@ export const IssueActivity: React.FC<Props> = (props) => {
|
|||||||
if (!workspaceSlug || !issueDetails) return;
|
if (!workspaceSlug || !issueDetails) return;
|
||||||
|
|
||||||
await issuesService
|
await issuesService
|
||||||
.createIssueComment(
|
.createIssueComment(workspaceSlug.toString(), issueDetails.project, issueDetails.id, formData, user)
|
||||||
workspaceSlug.toString(),
|
|
||||||
issueDetails.project,
|
|
||||||
issueDetails.id,
|
|
||||||
formData,
|
|
||||||
user
|
|
||||||
)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
mutate(PROJECT_ISSUES_ACTIVITY(issueDetails.id));
|
mutate(PROJECT_ISSUES_ACTIVITY(issueDetails.id));
|
||||||
})
|
})
|
||||||
@ -118,11 +94,7 @@ export const IssueActivity: React.FC<Props> = (props) => {
|
|||||||
<ul role="list" className="-mb-4">
|
<ul role="list" className="-mb-4">
|
||||||
{issueActivities?.map((activityItem, index) => {
|
{issueActivities?.map((activityItem, index) => {
|
||||||
// determines what type of action is performed
|
// determines what type of action is performed
|
||||||
const message = activityItem.field ? (
|
const message = activityItem.field ? <ActivityMessage activity={activityItem} /> : "created the issue.";
|
||||||
<ActivityMessage activity={activityItem} />
|
|
||||||
) : (
|
|
||||||
"created the issue."
|
|
||||||
);
|
|
||||||
|
|
||||||
if ("field" in activityItem && activityItem.field !== "updated_by") {
|
if ("field" in activityItem && activityItem.field !== "updated_by") {
|
||||||
return (
|
return (
|
||||||
@ -141,15 +113,11 @@ export const IssueActivity: React.FC<Props> = (props) => {
|
|||||||
<div className="ring-6 flex h-7 w-7 items-center justify-center rounded-full bg-custom-background-80 text-custom-text-200 ring-white">
|
<div className="ring-6 flex h-7 w-7 items-center justify-center rounded-full bg-custom-background-80 text-custom-text-200 ring-white">
|
||||||
{activityItem.field ? (
|
{activityItem.field ? (
|
||||||
activityItem.new_value === "restore" ? (
|
activityItem.new_value === "restore" ? (
|
||||||
<Icon
|
<Icon iconName="history" className="text-sm text-custom-text-200" />
|
||||||
iconName="history"
|
|
||||||
className="text-sm text-custom-text-200"
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<ActivityIcon activity={activityItem} />
|
<ActivityIcon activity={activityItem} />
|
||||||
)
|
)
|
||||||
) : activityItem.actor_detail.avatar &&
|
) : activityItem.actor_detail.avatar && activityItem.actor_detail.avatar !== "" ? (
|
||||||
activityItem.actor_detail.avatar !== "" ? (
|
|
||||||
<img
|
<img
|
||||||
src={activityItem.actor_detail.avatar}
|
src={activityItem.actor_detail.avatar}
|
||||||
alt={activityItem.actor_detail.display_name}
|
alt={activityItem.actor_detail.display_name}
|
||||||
@ -172,13 +140,10 @@ export const IssueActivity: React.FC<Props> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1 py-3">
|
<div className="min-w-0 flex-1 py-3">
|
||||||
<div className="text-xs text-custom-text-200 break-words">
|
<div className="text-xs text-custom-text-200 break-words">
|
||||||
{activityItem.field === "archived_at" &&
|
{activityItem.field === "archived_at" && activityItem.new_value !== "restore" ? (
|
||||||
activityItem.new_value !== "restore" ? (
|
|
||||||
<span className="text-gray font-medium">Plane</span>
|
<span className="text-gray font-medium">Plane</span>
|
||||||
) : activityItem.actor_detail.is_bot ? (
|
) : activityItem.actor_detail.is_bot ? (
|
||||||
<span className="text-gray font-medium">
|
<span className="text-gray font-medium">{activityItem.actor_detail.first_name} Bot</span>
|
||||||
{activityItem.actor_detail.first_name} Bot
|
|
||||||
</span>
|
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -190,10 +155,7 @@ export const IssueActivity: React.FC<Props> = (props) => {
|
|||||||
: activityItem.actor_detail.display_name}
|
: activityItem.actor_detail.display_name}
|
||||||
</button>
|
</button>
|
||||||
)}{" "}
|
)}{" "}
|
||||||
{message}{" "}
|
{message} <span className="whitespace-nowrap">{timeAgo(activityItem.created_at)}</span>
|
||||||
<span className="whitespace-nowrap">
|
|
||||||
{timeAgo(activityItem.created_at)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -217,10 +179,7 @@ export const IssueActivity: React.FC<Props> = (props) => {
|
|||||||
<AddComment
|
<AddComment
|
||||||
onSubmit={handleAddComment}
|
onSubmit={handleAddComment}
|
||||||
disabled={
|
disabled={
|
||||||
!allowed ||
|
!allowed || !issueDetails || issueDetails.state === "closed" || issueDetails.state === "archived"
|
||||||
!issueDetails ||
|
|
||||||
issueDetails.state === "closed" ||
|
|
||||||
issueDetails.state === "archived"
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR, { mutate } from "swr";
|
import useSWR, { mutate } from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
|
|
||||||
// react dropzone
|
// react dropzone
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
@ -61,12 +61,7 @@ export const IssueAttachments: React.FC<Props> = (props) => {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
issuesService
|
issuesService
|
||||||
.uploadIssueAttachment(
|
.uploadIssueAttachment(workspaceSlug as string, projectId as string, issueId as string, formData)
|
||||||
workspaceSlug as string,
|
|
||||||
projectId as string,
|
|
||||||
issueId as string,
|
|
||||||
formData
|
|
||||||
)
|
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
mutate<IIssueAttachment[]>(
|
mutate<IIssueAttachment[]>(
|
||||||
ISSUE_ATTACHMENTS(issueId as string),
|
ISSUE_ATTACHMENTS(issueId as string),
|
||||||
@ -109,12 +104,7 @@ export const IssueAttachments: React.FC<Props> = (props) => {
|
|||||||
const { data: attachments } = useSWR<IIssueAttachment[]>(
|
const { data: attachments } = useSWR<IIssueAttachment[]>(
|
||||||
workspaceSlug && projectId && issueId ? ISSUE_ATTACHMENTS(issueId as string) : null,
|
workspaceSlug && projectId && issueId ? ISSUE_ATTACHMENTS(issueId as string) : null,
|
||||||
workspaceSlug && projectId && issueId
|
workspaceSlug && projectId && issueId
|
||||||
? () =>
|
? () => issuesService.getIssueAttachment(workspaceSlug.toString(), projectId.toString(), issueId.toString())
|
||||||
issuesService.getIssueAttachment(
|
|
||||||
workspaceSlug.toString(),
|
|
||||||
projectId.toString(),
|
|
||||||
issueId.toString()
|
|
||||||
)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import { useRouter } from "next/router";
|
|||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
// import { LinkIcon, PlusIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
|
// import { LinkIcon, PlusIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||||
|
@ -11,7 +11,7 @@ import { mutate } from "swr";
|
|||||||
import { Control, Controller, useWatch } from "react-hook-form";
|
import { Control, Controller, useWatch } from "react-hook-form";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
|
|
||||||
// hooks
|
// hooks
|
||||||
import useUser from "hooks/use-user";
|
import useUser from "hooks/use-user";
|
||||||
@ -105,10 +105,7 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
|
|||||||
control={control}
|
control={control}
|
||||||
name="state"
|
name="state"
|
||||||
render={({ field: { value } }) => (
|
render={({ field: { value } }) => (
|
||||||
<StateSelect
|
<StateSelect value={value} onChange={(val: string) => submitChanges({ state: val })} />
|
||||||
value={value}
|
|
||||||
onChange={(val: string) => submitChanges({ state: val })}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -117,13 +114,7 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
|
|||||||
<div className="mb-[6px]">
|
<div className="mb-[6px]">
|
||||||
<div className="border border-custom-border-200 rounded-[4px] p-2 flex justify-between items-center">
|
<div className="border border-custom-border-200 rounded-[4px] p-2 flex justify-between items-center">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<svg
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
width="18"
|
|
||||||
height="18"
|
|
||||||
viewBox="0 0 18 18"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
<path
|
||||||
d="M13.5862 14.5239C13.3459 14.5239 13.1416 14.4398 12.9733 14.2715C12.805 14.1032 12.7209 13.8989 12.7209 13.6585V3.76429C12.7209 3.52391 12.805 3.31958 12.9733 3.15132C13.1416 2.98306 13.3459 2.89893 13.5862 2.89893C13.8266 2.89893 14.031 2.98306 14.1992 3.15132C14.3675 3.31958 14.4516 3.52391 14.4516 3.76429V13.6585C14.4516 13.8989 14.3675 14.1032 14.1992 14.2715C14.031 14.4398 13.8266 14.5239 13.5862 14.5239ZM5.1629 14.5239C5.04676 14.5239 4.93557 14.5018 4.82932 14.4576C4.72308 14.4133 4.63006 14.3513 4.55025 14.2715C4.47045 14.1917 4.40843 14.0986 4.36419 13.9922C4.31996 13.8858 4.29785 13.7746 4.29785 13.6585V11.2643C4.29785 11.0239 4.38198 10.8196 4.55025 10.6513C4.71851 10.4831 4.92283 10.3989 5.16322 10.3989C5.40359 10.3989 5.60791 10.4831 5.77618 10.6513C5.94445 10.8196 6.02859 11.0239 6.02859 11.2643V13.6585C6.02859 13.7746 6.00647 13.8858 5.96223 13.9922C5.91801 14.0986 5.85599 14.1917 5.77618 14.2715C5.69638 14.3513 5.60325 14.4133 5.49678 14.4576C5.39033 14.5018 5.27904 14.5239 5.1629 14.5239ZM9.37473 14.5239C9.13436 14.5239 8.93003 14.4398 8.76176 14.2715C8.59349 14.1032 8.50936 13.8989 8.50936 13.6585V7.5143C8.50936 7.27391 8.59349 7.06958 8.76176 6.90132C8.93003 6.73306 9.13436 6.64893 9.37473 6.64893C9.61511 6.64893 9.81943 6.73306 9.98771 6.90132C10.156 7.06958 10.2401 7.27391 10.2401 7.5143V13.6585C10.2401 13.8989 10.156 14.1032 9.98771 14.2715C9.81943 14.4398 9.61511 14.5239 9.37473 14.5239Z"
|
d="M13.5862 14.5239C13.3459 14.5239 13.1416 14.4398 12.9733 14.2715C12.805 14.1032 12.7209 13.8989 12.7209 13.6585V3.76429C12.7209 3.52391 12.805 3.31958 12.9733 3.15132C13.1416 2.98306 13.3459 2.89893 13.5862 2.89893C13.8266 2.89893 14.031 2.98306 14.1992 3.15132C14.3675 3.31958 14.4516 3.52391 14.4516 3.76429V13.6585C14.4516 13.8989 14.3675 14.1032 14.1992 14.2715C14.031 14.4398 13.8266 14.5239 13.5862 14.5239ZM5.1629 14.5239C5.04676 14.5239 4.93557 14.5018 4.82932 14.4576C4.72308 14.4133 4.63006 14.3513 4.55025 14.2715C4.47045 14.1917 4.40843 14.0986 4.36419 13.9922C4.31996 13.8858 4.29785 13.7746 4.29785 13.6585V11.2643C4.29785 11.0239 4.38198 10.8196 4.55025 10.6513C4.71851 10.4831 4.92283 10.3989 5.16322 10.3989C5.40359 10.3989 5.60791 10.4831 5.77618 10.6513C5.94445 10.8196 6.02859 11.0239 6.02859 11.2643V13.6585C6.02859 13.7746 6.00647 13.8858 5.96223 13.9922C5.91801 14.0986 5.85599 14.1917 5.77618 14.2715C5.69638 14.3513 5.60325 14.4133 5.49678 14.4576C5.39033 14.5018 5.27904 14.5239 5.1629 14.5239ZM9.37473 14.5239C9.13436 14.5239 8.93003 14.4398 8.76176 14.2715C8.59349 14.1032 8.50936 13.8989 8.50936 13.6585V7.5143C8.50936 7.27391 8.59349 7.06958 8.76176 6.90132C8.93003 6.73306 9.13436 6.64893 9.37473 6.64893C9.61511 6.64893 9.81943 6.73306 9.98771 6.90132C10.156 7.06958 10.2401 7.27391 10.2401 7.5143V13.6585C10.2401 13.8989 10.156 14.1032 9.98771 14.2715C9.81943 14.4398 9.61511 14.5239 9.37473 14.5239Z"
|
||||||
fill="#A3A3A3"
|
fill="#A3A3A3"
|
||||||
@ -137,10 +128,7 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
|
|||||||
control={control}
|
control={control}
|
||||||
name="priority"
|
name="priority"
|
||||||
render={({ field: { value } }) => (
|
render={({ field: { value } }) => (
|
||||||
<PrioritySelect
|
<PrioritySelect value={value} onChange={(val) => submitChanges({ priority: val })} />
|
||||||
value={value}
|
|
||||||
onChange={(val) => submitChanges({ priority: val })}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -157,10 +145,7 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
|
|||||||
control={control}
|
control={control}
|
||||||
name="assignees_list"
|
name="assignees_list"
|
||||||
render={({ field: { value } }) => (
|
render={({ field: { value } }) => (
|
||||||
<AssigneeSelect
|
<AssigneeSelect value={value} onChange={(val: string) => submitChanges({ assignees_list: [val] })} />
|
||||||
value={value}
|
|
||||||
onChange={(val: string) => submitChanges({ assignees_list: [val] })}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -180,10 +165,7 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
|
|||||||
control={control}
|
control={control}
|
||||||
name="estimate_point"
|
name="estimate_point"
|
||||||
render={({ field: { value } }) => (
|
render={({ field: { value } }) => (
|
||||||
<EstimateSelect
|
<EstimateSelect value={value} onChange={(val) => submitChanges({ estimate_point: val })} />
|
||||||
value={value}
|
|
||||||
onChange={(val) => submitChanges({ estimate_point: val })}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -201,10 +183,7 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
|
|||||||
control={control}
|
control={control}
|
||||||
name="parent"
|
name="parent"
|
||||||
render={({ field: { value } }) => (
|
render={({ field: { value } }) => (
|
||||||
<ParentSelect
|
<ParentSelect value={value} onChange={(val) => submitChanges({ parent: val })} />
|
||||||
value={value}
|
|
||||||
onChange={(val) => submitChanges({ parent: val })}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -224,22 +203,16 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
|
|||||||
if (!user || !workspaceSlug || !projectId || !issueId) return;
|
if (!user || !workspaceSlug || !projectId || !issueId) return;
|
||||||
|
|
||||||
issuesService
|
issuesService
|
||||||
.createIssueRelation(
|
.createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, user, {
|
||||||
workspaceSlug as string,
|
related_list: [
|
||||||
projectId as string,
|
...val.map((issue: any) => ({
|
||||||
issueId as string,
|
issue: issue.blocker_issue_detail.id,
|
||||||
user,
|
relation_type: "blocked_by" as const,
|
||||||
{
|
related_issue: issueId as string,
|
||||||
related_list: [
|
related_issue_detail: issue.blocker_issue_detail,
|
||||||
...val.map((issue: any) => ({
|
})),
|
||||||
issue: issue.blocker_issue_detail.id,
|
],
|
||||||
relation_type: "blocked_by" as const,
|
})
|
||||||
related_issue: issueId as string,
|
|
||||||
related_issue_detail: issue.blocker_issue_detail,
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
handleMutation({
|
handleMutation({
|
||||||
issue_relations: [
|
issue_relations: [
|
||||||
@ -315,22 +288,16 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
|
|||||||
if (!user || !workspaceSlug || !projectId || !issueId) return;
|
if (!user || !workspaceSlug || !projectId || !issueId) return;
|
||||||
|
|
||||||
issuesService
|
issuesService
|
||||||
.createIssueRelation(
|
.createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, user, {
|
||||||
workspaceSlug as string,
|
related_list: [
|
||||||
projectId as string,
|
...val.map((issue: any) => ({
|
||||||
issueId as string,
|
issue: issue.blocked_issue_detail.id,
|
||||||
user,
|
relation_type: "blocked_by" as const,
|
||||||
{
|
related_issue: issueId as string,
|
||||||
related_list: [
|
related_issue_detail: issue.blocked_issue_detail,
|
||||||
...val.map((issue: any) => ({
|
})),
|
||||||
issue: issue.blocked_issue_detail.id,
|
],
|
||||||
relation_type: "blocked_by" as const,
|
})
|
||||||
related_issue: issueId as string,
|
|
||||||
related_issue_detail: issue.blocked_issue_detail,
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
handleMutation({
|
handleMutation({
|
||||||
related_issues: [
|
related_issues: [
|
||||||
@ -429,13 +396,8 @@ export const IssuePropertiesDetail: React.FC<Props> = (props) => {
|
|||||||
onClick={() => setIsViewAllOpen((prev) => !prev)}
|
onClick={() => setIsViewAllOpen((prev) => !prev)}
|
||||||
className="w-full flex justify-center items-center gap-1 !py-2"
|
className="w-full flex justify-center items-center gap-1 !py-2"
|
||||||
>
|
>
|
||||||
<span className="text-base text-custom-primary-100">
|
<span className="text-base text-custom-primary-100">{isViewAllOpen ? "View less" : "View all"}</span>
|
||||||
{isViewAllOpen ? "View less" : "View all"}
|
<ChevronDown size={16} className={`ml-1 text-custom-primary-100 ${isViewAllOpen ? "-rotate-180" : ""}`} />
|
||||||
</span>
|
|
||||||
<ChevronDown
|
|
||||||
size={16}
|
|
||||||
className={`ml-1 text-custom-primary-100 ${isViewAllOpen ? "-rotate-180" : ""}`}
|
|
||||||
/>
|
|
||||||
</SecondaryButton>
|
</SecondaryButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,7 +8,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
|
|
||||||
// fetch key
|
// fetch key
|
||||||
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
||||||
@ -37,8 +37,7 @@ export const ParentSelect: React.FC<Props> = (props) => {
|
|||||||
const { data: issueDetails } = useSWR(
|
const { data: issueDetails } = useSWR(
|
||||||
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId.toString()) : null,
|
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId.toString()) : null,
|
||||||
workspaceSlug && projectId && issueId
|
workspaceSlug && projectId && issueId
|
||||||
? () =>
|
? () => issuesService.retrieve(workspaceSlug.toString(), projectId.toString(), issueId.toString())
|
||||||
issuesService.retrieve(workspaceSlug.toString(), projectId.toString(), issueId.toString())
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import useSWR from "swr";
|
|||||||
import { ChevronDown } from "lucide-react";
|
import { ChevronDown } from "lucide-react";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import stateService from "services/state.service";
|
import stateService from "services/project_state.service";
|
||||||
|
|
||||||
// fetch key
|
// fetch key
|
||||||
import { STATES_LIST } from "constants/fetch-keys";
|
import { STATES_LIST } from "constants/fetch-keys";
|
||||||
@ -39,9 +39,7 @@ export const StateSelect: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const { data: stateGroups } = useSWR(
|
const { data: stateGroups } = useSWR(
|
||||||
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
workspaceSlug && projectId ? STATES_LIST(projectId as string) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId ? () => stateService.getStates(workspaceSlug as string, projectId as string) : null
|
||||||
? () => stateService.getStates(workspaceSlug as string, projectId as string)
|
|
||||||
: null
|
|
||||||
);
|
);
|
||||||
const states = getStatesList(stateGroups);
|
const states = getStatesList(stateGroups);
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import useSWR, { mutate } from "swr";
|
|||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
|
|
||||||
// fetch key
|
// fetch key
|
||||||
import { SUB_ISSUES } from "constants/fetch-keys";
|
import { SUB_ISSUES } from "constants/fetch-keys";
|
||||||
@ -41,8 +41,7 @@ export const SubIssueList: React.FC<Props> = (props) => {
|
|||||||
const { data: subIssuesResponse } = useSWR(
|
const { data: subIssuesResponse } = useSWR(
|
||||||
workspaceSlug && issueDetails ? SUB_ISSUES(issueDetails.id) : null,
|
workspaceSlug && issueDetails ? SUB_ISSUES(issueDetails.id) : null,
|
||||||
workspaceSlug && issueDetails
|
workspaceSlug && issueDetails
|
||||||
? () =>
|
? () => issuesService.subIssues(workspaceSlug as string, issueDetails.project, issueDetails.id)
|
||||||
issuesService.subIssues(workspaceSlug as string, issueDetails.project, issueDetails.id)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
@ -28,8 +28,7 @@ const useGanttChartIssues = (workspaceSlug: string | undefined, projectId: strin
|
|||||||
const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
|
const { data: ganttIssues, mutate: mutateGanttIssues } = useSWR(
|
||||||
workspaceSlug && projectId ? PROJECT_ISSUES_LIST_WITH_PARAMS(projectId, params) : null,
|
workspaceSlug && projectId ? PROJECT_ISSUES_LIST_WITH_PARAMS(projectId, params) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? () =>
|
? () => issuesService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params)
|
||||||
issuesService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useIssuesView from "hooks/use-issues-view";
|
import useIssuesView from "hooks/use-issues-view";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
|
@ -7,7 +7,7 @@ import useSWR from "swr";
|
|||||||
// contexts
|
// contexts
|
||||||
import { issueViewContext } from "contexts/issue-view.context";
|
import { issueViewContext } from "contexts/issue-view.context";
|
||||||
// services
|
// services
|
||||||
import issuesService from "services/issues.service";
|
import issuesService from "services/issue.service";
|
||||||
import cyclesService from "services/cycles.service";
|
import cyclesService from "services/cycles.service";
|
||||||
import modulesService from "services/modules.service";
|
import modulesService from "services/modules.service";
|
||||||
// types
|
// types
|
||||||
@ -45,19 +45,14 @@ const useCalendarIssuesView = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { data: projectCalendarIssues, mutate: mutateProjectCalendarIssues } = useSWR(
|
const { data: projectCalendarIssues, mutate: mutateProjectCalendarIssues } = useSWR(
|
||||||
|
workspaceSlug && projectId ? PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params) : null,
|
||||||
workspaceSlug && projectId
|
workspaceSlug && projectId
|
||||||
? PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params)
|
? () => issuesService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params)
|
||||||
: null,
|
|
||||||
workspaceSlug && projectId
|
|
||||||
? () =>
|
|
||||||
issuesService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: cycleCalendarIssues, mutate: mutateCycleCalendarIssues } = useSWR(
|
const { data: cycleCalendarIssues, mutate: mutateCycleCalendarIssues } = useSWR(
|
||||||
workspaceSlug && projectId && cycleId
|
workspaceSlug && projectId && cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params) : null,
|
||||||
? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params)
|
|
||||||
: null,
|
|
||||||
workspaceSlug && projectId && cycleId
|
workspaceSlug && projectId && cycleId
|
||||||
? () =>
|
? () =>
|
||||||
cyclesService.getCycleIssuesWithParams(
|
cyclesService.getCycleIssuesWithParams(
|
||||||
@ -70,9 +65,7 @@ const useCalendarIssuesView = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { data: moduleCalendarIssues, mutate: mutateModuleCalendarIssues } = useSWR(
|
const { data: moduleCalendarIssues, mutate: mutateModuleCalendarIssues } = useSWR(
|
||||||
workspaceSlug && projectId && moduleId
|
workspaceSlug && projectId && moduleId ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params) : null,
|
||||||
? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params)
|
|
||||||
: null,
|
|
||||||
workspaceSlug && projectId && moduleId
|
workspaceSlug && projectId && moduleId
|
||||||
? () =>
|
? () =>
|
||||||
modulesService.getModuleIssuesWithParams(
|
modulesService.getModuleIssuesWithParams(
|
||||||
@ -87,8 +80,7 @@ const useCalendarIssuesView = () => {
|
|||||||
const { data: viewCalendarIssues, mutate: mutateViewCalendarIssues } = useSWR(
|
const { data: viewCalendarIssues, mutate: mutateViewCalendarIssues } = useSWR(
|
||||||
workspaceSlug && projectId && viewId && params ? VIEW_ISSUES(viewId.toString(), params) : null,
|
workspaceSlug && projectId && viewId && params ? VIEW_ISSUES(viewId.toString(), params) : null,
|
||||||
workspaceSlug && projectId && viewId && params
|
workspaceSlug && projectId && viewId && params
|
||||||
? () =>
|
? () => issuesService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params)
|
||||||
issuesService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params)
|
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import useSWR from "swr";
|
|||||||
import { COMMENT_REACTION_LIST } from "constants/fetch-keys";
|
import { COMMENT_REACTION_LIST } from "constants/fetch-keys";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import reactionService from "services/reaction.service";
|
import reactionService from "services/issue_reaction.service";
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
import { groupReactions } from "helpers/emoji.helper";
|
import { groupReactions } from "helpers/emoji.helper";
|
||||||
@ -69,8 +69,7 @@ const useCommentReaction = (
|
|||||||
if (!workspaceSlug || !projectId || !commendId) return;
|
if (!workspaceSlug || !projectId || !commendId) return;
|
||||||
|
|
||||||
mutateCommentReactions(
|
mutateCommentReactions(
|
||||||
(prevData: any) =>
|
(prevData: any) => prevData?.filter((r: any) => r.actor !== user?.user?.id || r.reaction !== reaction) || [],
|
||||||
prevData?.filter((r: any) => r.actor !== user?.user?.id || r.reaction !== reaction) || [],
|
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
|
||||||
// services
|
// services
|
||||||
import estimatesService from "services/estimates.service";
|
import estimatesService from "services/project_estimates.service";
|
||||||
// hooks
|
// hooks
|
||||||
import useProjectDetails from "hooks/use-project-details";
|
import useProjectDetails from "hooks/use-project-details";
|
||||||
// helpers
|
// helpers
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user