Merge branch 'develop' of https://github.com/makeplane/plane into style/views_card

This commit is contained in:
LAKHAN BAHETI 2023-11-14 19:08:33 +05:30
commit 63f5429c18
26 changed files with 223 additions and 208 deletions

View File

@ -51,7 +51,7 @@ const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
{activity.issue_detail ? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}` : "Issue"} {activity.issue_detail ? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}` : "Issue"}
<RocketIcon size={12} color="#6b7280" /> <RocketIcon size={12} color="#6b7280" className="flex-shrink-0" />
</a> </a>
</Tooltip> </Tooltip>
); );
@ -271,10 +271,10 @@ const activityDetails: {
href={`/${workspaceSlug}/projects/${activity.project}/cycles/${activity.new_identifier}`} href={`/${workspaceSlug}/projects/${activity.project}/cycles/${activity.new_identifier}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="w-full font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
{activity.new_value} <span className="truncate">{activity.new_value}</span>
<RocketIcon size={12} color="#6b7280" /> <RocketIcon size={12} color="#6b7280" className="flex-shrink-0" />
</a> </a>
</> </>
); );
@ -286,10 +286,10 @@ const activityDetails: {
href={`/${workspaceSlug}/projects/${activity.project}/cycles/${activity.new_identifier}`} href={`/${workspaceSlug}/projects/${activity.project}/cycles/${activity.new_identifier}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="w-full font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
{activity.new_value} <span className="truncate">{activity.new_value}</span>
<RocketIcon size={12} color="#6b7280" /> <RocketIcon size={12} color="#6b7280" className="flex-shrink-0" />
</a> </a>
</> </>
); );
@ -301,10 +301,10 @@ const activityDetails: {
href={`/${workspaceSlug}/projects/${activity.project}/cycles/${activity.old_identifier}`} href={`/${workspaceSlug}/projects/${activity.project}/cycles/${activity.old_identifier}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="w-full font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
{activity.old_value} <span className="truncate">{activity.old_value}</span>
<RocketIcon size={12} color="#6b7280" /> <RocketIcon size={12} color="#6b7280" className="flex-shrink-0" />
</a> </a>
</> </>
); );
@ -482,10 +482,10 @@ const activityDetails: {
href={`/${workspaceSlug}/projects/${activity.project}/modules/${activity.new_identifier}`} href={`/${workspaceSlug}/projects/${activity.project}/modules/${activity.new_identifier}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="w-full font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
{activity.new_value} <span className="truncate">{activity.new_value}</span>
<RocketIcon size={12} color="#6b7280" /> <RocketIcon size={12} color="#6b7280" className="flex-shrink-0" />
</a> </a>
</> </>
); );
@ -497,10 +497,10 @@ const activityDetails: {
href={`/${workspaceSlug}/projects/${activity.project}/modules/${activity.new_identifier}`} href={`/${workspaceSlug}/projects/${activity.project}/modules/${activity.new_identifier}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="w-full font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
{activity.new_value} <span className="truncate">{activity.new_value}</span>
<RocketIcon size={12} color="#6b7280" /> <RocketIcon size={12} color="#6b7280" className="flex-shrink-0" />
</a> </a>
</> </>
); );
@ -512,10 +512,10 @@ const activityDetails: {
href={`/${workspaceSlug}/projects/${activity.project}/modules/${activity.old_identifier}`} href={`/${workspaceSlug}/projects/${activity.project}/modules/${activity.old_identifier}`}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline" className="w-full font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
> >
{activity.old_value} <span className="truncate">{activity.old_value}</span>
<RocketIcon size={12} color="#6b7280" /> <RocketIcon size={12} color="#6b7280" className="flex-shrink-0" />
</a> </a>
</> </>
); );

View File

@ -80,7 +80,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
workspaceSlug && projectId ? () => cycleStore.fetchCycles(workspaceSlug, projectId, "current") : null workspaceSlug && projectId ? () => cycleStore.fetchCycles(workspaceSlug, projectId, "current") : null
); );
const activeCycle = cycleStore.cycles?.[projectId]?.active || null; const activeCycle = cycleStore.cycles?.[projectId]?.current || null;
const cycle = activeCycle ? activeCycle[0] : null; const cycle = activeCycle ? activeCycle[0] : null;
const issues = (cycleStore?.active_cycle_issues as any) || null; const issues = (cycleStore?.active_cycle_issues as any) || null;

View File

@ -2,7 +2,7 @@ import { useCallback, useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { ArrowLeft, Circle, ExternalLink, Plus } from "lucide-react"; import { ArrowLeft, Briefcase, Circle, ExternalLink, Plus } from "lucide-react";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
@ -121,17 +121,23 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
<Breadcrumbs.BreadcrumbItem <Breadcrumbs.BreadcrumbItem
type="text" type="text"
icon={ icon={
currentProjectDetails?.emoji ? ( currentProjectDetails ? (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase"> currentProjectDetails?.emoji ? (
{renderEmoji(currentProjectDetails.emoji)} <span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
</span> {renderEmoji(currentProjectDetails.emoji)}
) : currentProjectDetails?.icon_prop ? ( </span>
<div className="h-7 w-7 flex-shrink-0 grid place-items-center"> ) : currentProjectDetails?.icon_prop ? (
{renderEmoji(currentProjectDetails.icon_prop)} <div className="h-7 w-7 flex-shrink-0 grid place-items-center">
</div> {renderEmoji(currentProjectDetails.icon_prop)}
</div>
) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{currentProjectDetails?.name.charAt(0)}
</span>
)
) : ( ) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white"> <span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
{currentProjectDetails?.name.charAt(0)} <Briefcase className="h-4 w-4" />
</span> </span>
) )
} }

View File

@ -542,13 +542,13 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
)} )}
{(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && ( {(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && (
<> <>
<CustomMenu {watch("parent") ? (
customButton={ <CustomMenu
<button customButton={
type="button" <button
className="flex items-center justify-between gap-1 w-full cursor-pointer rounded border-[0.5px] border-custom-border-300 text-custom-text-200 px-2 py-1 text-xs hover:bg-custom-background-80" type="button"
> className="flex items-center justify-between gap-1 w-full cursor-pointer rounded border-[0.5px] border-custom-border-300 text-custom-text-200 px-2 py-1 text-xs hover:bg-custom-background-80"
{watch("parent") ? ( >
<div className="flex items-center gap-1 text-custom-text-200"> <div className="flex items-center gap-1 text-custom-text-200">
<LayoutPanelTop className="h-3 w-3 flex-shrink-0" /> <LayoutPanelTop className="h-3 w-3 flex-shrink-0" />
<span className="whitespace-nowrap"> <span className="whitespace-nowrap">
@ -557,31 +557,30 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
${selectedParentIssue.sequence_id}`} ${selectedParentIssue.sequence_id}`}
</span> </span>
</div> </div>
) : ( </button>
<div className="flex items-center gap-1 text-custom-text-300"> }
<LayoutPanelTop className="h-3 w-3 flex-shrink-0" /> placement="bottom-start"
<span className="whitespace-nowrap">Add Parent</span> >
</div>
)}
</button>
}
placement="bottom-start"
>
{watch("parent") ? (
<>
<CustomMenu.MenuItem className="!p-1" onClick={() => setParentIssueListModalOpen(true)}>
Change parent issue
</CustomMenu.MenuItem>
<CustomMenu.MenuItem className="!p-1" onClick={() => setValue("parent", null)}>
Remove parent issue
</CustomMenu.MenuItem>
</>
) : (
<CustomMenu.MenuItem className="!p-1" onClick={() => setParentIssueListModalOpen(true)}> <CustomMenu.MenuItem className="!p-1" onClick={() => setParentIssueListModalOpen(true)}>
Select Parent Issue Change parent issue
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
)} <CustomMenu.MenuItem className="!p-1" onClick={() => setValue("parent", null)}>
</CustomMenu> Remove parent issue
</CustomMenu.MenuItem>
</CustomMenu>
) : (
<button
type="button"
className="flex items-center justify-between gap-1 w-min cursor-pointer rounded border-[0.5px] border-custom-border-300 text-custom-text-200 px-2 py-1 text-xs hover:bg-custom-background-80"
onClick={() => setParentIssueListModalOpen(true)}
>
<div className="flex items-center gap-1 text-custom-text-300">
<LayoutPanelTop className="h-3 w-3 flex-shrink-0" />
<span className="whitespace-nowrap">Add Parent</span>
</div>
</button>
)}
<Controller <Controller
control={control} control={control}
name="parent" name="parent"

View File

@ -49,7 +49,7 @@ export const FilterAssignees: React.FC<Props> = (props) => {
key={`assignees-${member.id}`} key={`assignees-${member.id}`}
isChecked={appliedFilters?.includes(member.id) ? true : false} isChecked={appliedFilters?.includes(member.id) ? true : false}
onClick={() => handleUpdate(member.id)} onClick={() => handleUpdate(member.id)}
icon={<Avatar name={member.display_name} src={member.avatar} showTooltip={false} size="sm" />} icon={<Avatar name={member.display_name} src={member.avatar} showTooltip={false} size="md" />}
title={member.display_name} title={member.display_name}
/> />
))} ))}

View File

@ -49,7 +49,7 @@ export const FilterCreatedBy: React.FC<Props> = (props) => {
key={`created-by-${member.id}`} key={`created-by-${member.id}`}
isChecked={appliedFilters?.includes(member.id) ? true : false} isChecked={appliedFilters?.includes(member.id) ? true : false}
onClick={() => handleUpdate(member.id)} onClick={() => handleUpdate(member.id)}
icon={<Avatar name={member.display_name} src={member.avatar} size="sm" />} icon={<Avatar name={member.display_name} src={member.avatar} size="md" />}
title={member.display_name} title={member.display_name}
/> />
))} ))}

View File

@ -49,7 +49,7 @@ export const FilterMentions: React.FC<Props> = (props) => {
key={`mentions-${member.id}`} key={`mentions-${member.id}`}
isChecked={appliedFilters?.includes(member.id) ? true : false} isChecked={appliedFilters?.includes(member.id) ? true : false}
onClick={() => handleUpdate(member.id)} onClick={() => handleUpdate(member.id)}
icon={<Avatar name={member?.display_name} src={member?.avatar} showTooltip={false} />} icon={<Avatar name={member?.display_name} src={member?.avatar} showTooltip={false} size={"md"} />}
title={member.display_name} title={member.display_name}
/> />
))} ))}

View File

@ -117,8 +117,8 @@ export const IssuePropertyAssignee: React.FC<IIssuePropertyAssignee> = observer(
) : ( ) : (
<span <span
className={`flex items-center justify-between gap-1 h-full w-full text-xs rounded duration-300 focus:outline-none ${ className={`flex items-center justify-between gap-1 h-full w-full text-xs rounded duration-300 focus:outline-none ${
noLabelBorder ? "" : " px-2.5 py-1 border border-custom-border-300" noLabelBorder ? "" : " px-2.5 py-1 border-[0.5px] border-custom-border-300"
}}`} }`}
> >
<User2 className="h-3 w-3" /> <User2 className="h-3 w-3" />
</span> </span>

View File

@ -78,7 +78,7 @@ export const IssueColumn: React.FC<Props> = ({
<div className="group flex items-center w-[28rem] text-sm h-11 sticky top-0 bg-custom-background-100 truncate border-b border-custom-border-100"> <div className="group flex items-center w-[28rem] text-sm h-11 sticky top-0 bg-custom-background-100 truncate border-b border-custom-border-100">
{properties.key && ( {properties.key && (
<div <div
className="flex gap-1.5 px-4 pr-0 py-2.5 items-center min-w-[96px]" className="flex gap-1.5 px-4 pr-0 py-2.5 items-center min-w-min"
style={issue.parent && nestingLevel !== 0 ? { paddingLeft } : {}} style={issue.parent && nestingLevel !== 0 ? { paddingLeft } : {}}
> >
<div className="relative flex items-center cursor-pointer text-xs text-center hover:text-custom-text-100"> <div className="relative flex items-center cursor-pointer text-xs text-center hover:text-custom-text-100">

View File

@ -94,7 +94,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
{displayProperties.key && ( {displayProperties.key && (
<span className="flex items-center px-4 py-2.5 h-full w-24 flex-shrink-0">ID</span> <span className="flex items-center px-4 py-2.5 h-full w-24 flex-shrink-0">ID</span>
)} )}
<span className="flex items-center px-4 py-2.5 h-full w-full flex-grow">Issue</span> <span className="flex items-center justify-center px-4 py-2.5 h-full w-full flex-grow">Issue</span>
</div> </div>
{issues.map((issue, index) => ( {issues.map((issue, index) => (

View File

@ -55,9 +55,9 @@ export const IssueCycleSelect: React.FC<IssueCycleSelectProps> = observer((props
query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase())); query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase()));
const label = selectedCycle ? ( const label = selectedCycle ? (
<div className="flex items-center gap-1 text-custom-text-200"> <div className="flex items-center w-full gap-1 text-custom-text-200">
<ContrastIcon className="h-3 w-3" /> <ContrastIcon className="h-3 w-3 flex-shrink-0" />
<div className="truncate">{selectedCycle.name}</div> <div className="truncate max-w-[160px]">{selectedCycle.name}</div>
</div> </div>
) : ( ) : (
<div className="flex items-center gap-1 text-custom-text-300"> <div className="flex items-center gap-1 text-custom-text-300">

View File

@ -3,16 +3,13 @@ import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
import { Combobox, Transition } from "@headlessui/react"; import { Combobox, Transition } from "@headlessui/react";
import { usePopper } from "react-popper"; import { usePopper } from "react-popper";
// services import { observer } from "mobx-react-lite";
import { IssueLabelService } from "services/issue"; // store
import { useMobxStore } from "lib/mobx/store-provider";
// ui // ui
import { IssueLabelsList } from "components/ui"; import { IssueLabelsList } from "components/ui";
// icons // icons
import { Check, Component, Plus, Search, Tag } from "lucide-react"; import { Check, Component, Plus, Search, Tag } from "lucide-react";
// types
import type { IIssueLabels } from "types";
// fetch-keys
import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys";
type Props = { type Props = {
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>; setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
@ -22,15 +19,19 @@ type Props = {
label?: JSX.Element; label?: JSX.Element;
}; };
const issueLabelService = new IssueLabelService(); export const IssueLabelSelect: React.FC<Props> = observer((props) => {
const { setIsOpen, value, onChange, projectId, label } = props;
export const IssueLabelSelect: React.FC<Props> = ({ setIsOpen, value, onChange, projectId, label }) => {
// states // states
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const {
project: { labels, fetchProjectLabels },
} = useMobxStore();
const [referenceElement, setReferenceElement] = useState<HTMLDivElement | null>(null); const [referenceElement, setReferenceElement] = useState<HTMLDivElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null); const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
@ -38,11 +39,11 @@ export const IssueLabelSelect: React.FC<Props> = ({ setIsOpen, value, onChange,
placement: "bottom-start", placement: "bottom-start",
}); });
const { data: issueLabels } = useSWR<IIssueLabels[]>( const issueLabels = labels?.[projectId] || [];
projectId ? PROJECT_ISSUE_LABELS(projectId) : null,
workspaceSlug && projectId useSWR(
? () => issueLabelService.getProjectIssueLabels(workspaceSlug as string, projectId) workspaceSlug && projectId ? `PROJECT_ISSUE_LABELS_${projectId.toUpperCase()}` : null,
: null workspaceSlug && projectId ? () => fetchProjectLabels(workspaceSlug.toString(), projectId) : null
); );
const filteredOptions = const filteredOptions =
@ -202,4 +203,4 @@ export const IssueLabelSelect: React.FC<Props> = ({ setIsOpen, value, onChange,
)} )}
</Combobox> </Combobox>
); );
}; });

View File

@ -55,9 +55,9 @@ export const IssueModuleSelect: React.FC<IssueModuleSelectProps> = observer((pro
query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase())); query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase()));
const label = selectedModule ? ( const label = selectedModule ? (
<div className="flex items-center gap-1 text-custom-text-200"> <div className="flex items-center w-full gap-1 text-custom-text-200">
<DiceIcon className="h-3 w-3" /> <DiceIcon className="h-3 w-3 flex-shrink-0" />
<span className="truncate">{selectedModule.name}</span> <span className="truncate max-w-[160px]">{selectedModule.name}</span>
</div> </div>
) : ( ) : (
<div className="flex items-center gap-1 text-custom-text-300"> <div className="flex items-center gap-1 text-custom-text-300">

View File

@ -1,14 +1,13 @@
import React from "react"; import React from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
// services import { observer } from "mobx-react-lite";
import { ProjectStateService } from "services/project"; // store
import { useMobxStore } from "lib/mobx/store-provider";
// ui // ui
import { CustomSearchSelect, DoubleCircleIcon, StateGroupIcon } from "@plane/ui"; import { CustomSearchSelect, DoubleCircleIcon, StateGroupIcon } from "@plane/ui";
// icons // icons
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
// fetch keys
import { STATES_LIST } from "constants/fetch-keys";
type Props = { type Props = {
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>; setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
@ -17,19 +16,24 @@ type Props = {
projectId: string; projectId: string;
}; };
// services export const IssueStateSelect: React.FC<Props> = observer((props) => {
const projectStateService = new ProjectStateService(); const { setIsOpen, value, onChange, projectId } = props;
export const IssueStateSelect: React.FC<Props> = ({ setIsOpen, value, onChange, projectId }) => {
// states // states
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const { data: states } = useSWR( const {
workspaceSlug && projectId ? STATES_LIST(projectId) : null, projectState: { states: projectStates, fetchProjectStates },
workspaceSlug && projectId ? () => projectStateService.getStates(workspaceSlug as string, projectId) : null } = useMobxStore();
useSWR(
workspaceSlug && projectId ? `STATES_LIST_${projectId.toUpperCase()}` : null,
workspaceSlug && projectId ? () => fetchProjectStates(workspaceSlug.toString(), projectId) : null
); );
const states = projectStates?.[projectId] || [];
const options = states?.map((state) => ({ const options = states?.map((state) => ({
value: state.id, value: state.id,
query: state.name, query: state.name,
@ -74,4 +78,4 @@ export const IssueStateSelect: React.FC<Props> = ({ setIsOpen, value, onChange,
noChevron noChevron
/> />
); );
}; });

View File

@ -102,7 +102,7 @@ export const DeleteModuleModal: React.FC<Props> = observer((props) => {
<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 module-{" "} Are you sure you want to delete module-{" "}
<span className="break-words font-medium text-custom-text-100">{data?.name}</span>? All of the <span className="break-all font-medium text-custom-text-100">{data?.name}</span>? All of the
data related to the module will be permanently removed. This action cannot be undone. data related to the module will be permanently removed. This action cannot be undone.
</p> </p>
</div> </div>

View File

@ -6,6 +6,7 @@ import { ProjectMemberService } from "services/project";
// ui // ui
import { Avatar, CustomSearchSelect } from "@plane/ui"; import { Avatar, CustomSearchSelect } from "@plane/ui";
// icons // icons
import { Combobox } from "@headlessui/react";
import { UserCircle } from "lucide-react"; import { UserCircle } from "lucide-react";
// fetch-keys // fetch-keys
import { PROJECT_MEMBERS } from "constants/fetch-keys"; import { PROJECT_MEMBERS } from "constants/fetch-keys";
@ -59,6 +60,16 @@ export const ModuleLeadSelect: React.FC<Props> = ({ value, onChange }) => {
)} )}
</div> </div>
} }
footerOption={
<Combobox.Option
value=""
className="flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 text-custom-text-200"
>
<span className="flex items-center justify-start gap-1 text-custom-text-200">
<span>No Lead</span>
</span>
</Combobox.Option>
}
onChange={onChange} onChange={onChange}
noChevron noChevron
/> />

View File

@ -58,8 +58,8 @@ export const SinglePageDetailedItem: React.FC<TSingleStatProps> = ({
<Link href={`/${workspaceSlug}/projects/${projectId}/pages/${page.id}`}> <Link href={`/${workspaceSlug}/projects/${projectId}/pages/${page.id}`}>
<a className="block p-4"> <a className="block p-4">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2"> <div className="flex items-center overflow-hidden gap-2">
<p className="mr-2 truncate text-sm">{truncateText(page.name, 75)}</p> <p className="mr-2 truncate text-sm">{page.name}</p>
{page.label_details.length > 0 && {page.label_details.length > 0 &&
page.label_details.map((label) => ( page.label_details.map((label) => (
<div <div
@ -188,9 +188,13 @@ export const SinglePageDetailedItem: React.FC<TSingleStatProps> = ({
</CustomMenu> </CustomMenu>
</div> </div>
</div> </div>
<div className="relative mt-2 space-y-2 text-sm text-custom-text-200"> {page.blocks.length > 0 && (
{page.blocks.length > 0 ? page.blocks.slice(0, 3).map((block) => <h4>{block.name}</h4>) : null} <div className="relative mt-2 space-y-2 text-sm text-custom-text-200">
</div> {page.blocks.slice(0, 3).map((block) => (
<h4 className="truncate">{block.name}</h4>
))}
</div>
)}
</a> </a>
</Link> </Link>
</div> </div>

View File

@ -59,9 +59,9 @@ export const SinglePageListItem: React.FC<TSingleStatProps> = ({
<a> <a>
<div className="relative rounded p-4 text-custom-text-200 hover:bg-custom-background-80"> <div className="relative rounded p-4 text-custom-text-200 hover:bg-custom-background-80">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex flex-wrap items-center gap-2"> <div className="flex overflow-hidden items-center gap-2">
<FileText className="h-4 w-4" /> <FileText className="h-4 w-4 shrink-0" />
<p className="mr-2 truncate text-sm text-custom-text-100">{truncateText(page.name, 75)}</p> <p className="mr-2 truncate text-sm text-custom-text-100">{page.name}</p>
{page.label_details.length > 0 && {page.label_details.length > 0 &&
page.label_details.map((label) => ( page.label_details.map((label) => (
<div <div

View File

@ -73,7 +73,7 @@ export const JoinProjectModal: React.FC<TJoinProjectModalProps> = (props) => {
Join Project? Join Project?
</Dialog.Title> </Dialog.Title>
<p> <p>
Are you sure you want to join the project <span className="font-semibold">{project?.name}</span>? Are you sure you want to join the project <span className="font-semibold break-words">{project?.name}</span>?
Please click the &apos;Join Project&apos; button below to continue. Please click the &apos;Join Project&apos; button below to continue.
</p> </p>
<div className="space-y-3" /> <div className="space-y-3" />

View File

@ -5,18 +5,7 @@ import { DraggableProvided, DraggableStateSnapshot } from "@hello-pangea/dnd";
import { Disclosure, Transition } from "@headlessui/react"; import { Disclosure, Transition } from "@headlessui/react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// icons // icons
import { import { MoreVertical, PenSquare, LinkIcon, Star, FileText, Settings, Share2, LogOut, ChevronDown } from "lucide-react";
MoreVertical,
PenSquare,
LinkIcon,
Star,
Trash2,
FileText,
Settings,
Share2,
LogOut,
ChevronDown,
} from "lucide-react";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// helpers // helpers
@ -27,7 +16,7 @@ import { IProject } from "types";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { CustomMenu, Tooltip, ArchiveIcon, PhotoFilterIcon, DiceIcon, ContrastIcon, LayersIcon } from "@plane/ui"; import { CustomMenu, Tooltip, ArchiveIcon, PhotoFilterIcon, DiceIcon, ContrastIcon, LayersIcon } from "@plane/ui";
import { LeaveProjectModal, DeleteProjectModal, PublishProjectModal } from "components/project"; import { LeaveProjectModal, PublishProjectModal } from "components/project";
type Props = { type Props = {
project: IProject; project: IProject;
@ -71,6 +60,7 @@ const navigation = (workspaceSlug: string, projectId: string) => [
]; ];
export const ProjectSidebarListItem: React.FC<Props> = observer((props) => { export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { project, provided, snapshot, handleCopyText, shortContextMenu = false } = props; const { project, provided, snapshot, handleCopyText, shortContextMenu = false } = props;
// store // store
const { project: projectStore, theme: themeStore } = useMobxStore(); const { project: projectStore, theme: themeStore } = useMobxStore();
@ -81,7 +71,6 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
// states // states
const [leaveProjectModalOpen, setLeaveProjectModal] = useState(false); const [leaveProjectModalOpen, setLeaveProjectModal] = useState(false);
const [deleteProjectModalOpen, setDeleteProjectModal] = useState(false);
const [publishModalOpen, setPublishModal] = useState(false); const [publishModalOpen, setPublishModal] = useState(false);
const isAdmin = project.member_role === 20; const isAdmin = project.member_role === 20;
@ -121,21 +110,11 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
setLeaveProjectModal(false); setLeaveProjectModal(false);
}; };
const handleDeleteProjectClick = () => {
setDeleteProjectModal(true);
};
const handleDeleteProjectModalClose = () => {
setDeleteProjectModal(false);
router.push(`/${workspaceSlug}/projects`);
};
return ( return (
<> <>
<PublishProjectModal isOpen={publishModalOpen} project={project} onClose={() => setPublishModal(false)} /> <PublishProjectModal isOpen={publishModalOpen} project={project} onClose={() => setPublishModal(false)} />
<DeleteProjectModal project={project} isOpen={deleteProjectModalOpen} onClose={handleDeleteProjectModalClose} />
<LeaveProjectModal project={project} isOpen={leaveProjectModalOpen} onClose={handleLeaveProjectModalClose} /> <LeaveProjectModal project={project} isOpen={leaveProjectModalOpen} onClose={handleLeaveProjectModalClose} />
<Disclosure key={project.id} defaultOpen={projectId === project.id}> <Disclosure key={`${project.id} ${projectId}`} defaultOpen={projectId === project.id}>
{({ open }) => ( {({ open }) => (
<> <>
<div <div
@ -186,9 +165,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
</span> </span>
)} )}
{!isCollapsed && ( {!isCollapsed && <p className={`truncate text-custom-sidebar-text-200`}>{project.name}</p>}
<p className={`truncate text-custom-sidebar-text-200`}>{project.name}</p>
)}
</div> </div>
{!isCollapsed && ( {!isCollapsed && (
<ChevronDown <ChevronDown
@ -278,15 +255,6 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
</div> </div>
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
)} )}
{!shortContextMenu && isAdmin && (
<CustomMenu.MenuItem onClick={handleDeleteProjectClick}>
<span className="flex items-center justify-start gap-2 ">
<Trash2 className="h-3.5 w-3.5 stroke-[1.5]" />
<span>Delete project</span>
</span>
</CustomMenu.MenuItem>
)}
</CustomMenu> </CustomMenu>
)} )}
</div> </div>

View File

@ -9,7 +9,7 @@ import { AppliedFiltersList, FilterSelection, FiltersDropdown } from "components
// ui // ui
import { Button, Input, TextArea } from "@plane/ui"; import { Button, Input, TextArea } from "@plane/ui";
// types // types
import { IProjectView } from "types"; import { IProjectView, IIssueFilterOptions } from "types";
// constants // constants
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue"; import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
@ -43,7 +43,34 @@ export const ProjectViewForm: React.FC<Props> = observer(({ handleFormSubmit, ha
defaultValues, defaultValues,
}); });
const selectedFilters = watch("query_data"); const selectedFilters: IIssueFilterOptions = {};
Object.entries(watch("query_data") ?? {}).forEach(([key, value]) => {
if (!value) return;
if (Array.isArray(value) && value.length === 0) return;
selectedFilters[key as keyof IIssueFilterOptions] = value;
});
// for removing filters from a key
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
if (!value) return;
const newValues = selectedFilters?.[key] ?? [];
if (Array.isArray(value)) {
value.forEach((val) => {
if (newValues.includes(val)) newValues.splice(newValues.indexOf(val), 1);
});
} else {
if (selectedFilters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
}
setValue("query_data", {
...selectedFilters,
[key]: newValues,
});
};
const handleCreateUpdateView = async (formData: IProjectView) => { const handleCreateUpdateView = async (formData: IProjectView) => {
await handleFormSubmit(formData); await handleFormSubmit(formData);
@ -106,7 +133,7 @@ export const ProjectViewForm: React.FC<Props> = observer(({ handleFormSubmit, ha
id="description" id="description"
name="description" name="description"
placeholder="Description" placeholder="Description"
className="resize-none text-sm" className="h-24 w-full resize-none text-sm"
hasError={Boolean(errors?.description)} hasError={Boolean(errors?.description)}
value={value} value={value}
onChange={onChange} onChange={onChange}
@ -153,10 +180,10 @@ export const ProjectViewForm: React.FC<Props> = observer(({ handleFormSubmit, ha
<AppliedFiltersList <AppliedFiltersList
appliedFilters={selectedFilters} appliedFilters={selectedFilters}
handleClearAllFilters={clearAllFilters} handleClearAllFilters={clearAllFilters}
handleRemoveFilter={() => {}} handleRemoveFilter={handleRemoveFilter}
labels={projectStore.projectLabels ?? undefined} labels={projectStore.projectLabels ?? []}
members={projectMembers?.map((m) => m.member) ?? undefined} members={projectMembers?.map((m) => m.member) ?? []}
states={projectStateStore.projectStates ?? undefined} states={projectStateStore.projectStates ?? []}
/> />
</div> </div>
)} )}

View File

@ -60,12 +60,12 @@ export const ProjectViewListItem: React.FC<Props> = observer((props) => {
<Link href={`/${workspaceSlug}/projects/${projectId}/views/${view.id}`}> <Link href={`/${workspaceSlug}/projects/${projectId}/views/${view.id}`}>
<a className="flex items-center justify-between relative rounded p-4 w-full"> <a className="flex items-center justify-between relative rounded p-4 w-full">
<div className="flex items-center justify-between w-full"> <div className="flex items-center justify-between w-full">
<div className="flex items-center gap-4 overflow-auto"> <div className="flex items-center gap-4 overflow-hidden">
<div className="grid place-items-center flex-shrink-0 h-10 w-10 rounded bg-custom-background-90 group-hover:bg-custom-background-100"> <div className="grid place-items-center flex-shrink-0 h-10 w-10 rounded bg-custom-background-90 group-hover:bg-custom-background-100">
<PhotoFilterIcon className="h-3.5 w-3.5" /> <PhotoFilterIcon className="h-3.5 w-3.5" />
</div> </div>
<div className="flex flex-col overflow-auto"> <div className="flex flex-col overflow-hidden ">
<p className="text-sm leading-4 font-medium break-all truncate">{view.name}</p> <p className="text-sm leading-4 font-medium truncate break-all">{view.name}</p>
{view?.description && <p className="text-xs text-custom-text-200 break-all">{view.description}</p>} {view?.description && <p className="text-xs text-custom-text-200 break-all">{view.description}</p>}
</div> </div>
</div> </div>

View File

@ -95,27 +95,37 @@ export const WorkspaceMemberSelect: FC<IWorkspaceMemberSelect> = (props) => {
<div className={`mt-2 space-y-1 max-h-48 overflow-y-scroll`}> <div className={`mt-2 space-y-1 max-h-48 overflow-y-scroll`}>
{filteredOptions ? ( {filteredOptions ? (
filteredOptions.length > 0 ? ( filteredOptions.length > 0 ? (
filteredOptions.map((workspaceMember: IWorkspaceMember) => ( <>
{filteredOptions.map((workspaceMember: IWorkspaceMember) => (
<Listbox.Option
key={workspaceMember.id}
value={workspaceMember}
className={({ active, selected }) =>
`flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${
active && !selected ? "bg-custom-background-80" : ""
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
}
>
{({ selected }) => (
<>
<div className="flex items-center gap-2">
<Avatar name={workspaceMember?.member.display_name} src={workspaceMember?.member.avatar} />
{workspaceMember.member.display_name}
</div>
{selected && <Check className="h-3.5 w-3.5" />}
</>
)}
</Listbox.Option>
))}
<Listbox.Option <Listbox.Option
key={workspaceMember.id} value=""
value={workspaceMember} className="flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 text-custom-text-200"
className={({ active, selected }) =>
`flex items-center justify-between gap-2 cursor-pointer select-none truncate rounded px-1 py-1.5 ${
active && !selected ? "bg-custom-background-80" : ""
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
}
> >
{({ selected }) => ( <span className="flex items-center justify-start gap-1 text-custom-text-200">
<> <span>No Lead</span>
<div className="flex items-center gap-2"> </span>
<Avatar name={workspaceMember?.member.display_name} src={workspaceMember?.member.avatar} />
{workspaceMember.member.display_name}
</div>
{selected && <Check className="h-3.5 w-3.5" />}
</>
)}
</Listbox.Option> </Listbox.Option>
)) </>
) : ( ) : (
<span className="flex items-center gap-2 p-1"> <span className="flex items-center gap-2 p-1">
<p className="text-left text-custom-text-200 ">No matching results</p> <p className="text-left text-custom-text-200 ">No matching results</p>

View File

@ -114,7 +114,7 @@ export const WorkspaceViewForm: React.FC<Props> = observer((props) => {
value={value} value={value}
placeholder="Description" placeholder="Description"
onChange={onChange} onChange={onChange}
className="h-32 resize-none text-sm" className="h-24 w-full resize-none text-sm"
hasError={Boolean(errors?.description)} hasError={Boolean(errors?.description)}
/> />
)} )}

View File

@ -1,14 +1,12 @@
import React, { ReactElement } from "react"; import React, { ReactElement } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR from "swr"; import { observer } from "mobx-react-lite";
// services // store
import { ProjectService, ProjectMemberService } from "services/project"; import { useMobxStore } from "lib/mobx/store-provider";
// layouts // layouts
import { AppLayout } from "layouts/app-layout"; import { AppLayout } from "layouts/app-layout";
import { ProjectSettingLayout } from "layouts/settings-layout"; import { ProjectSettingLayout } from "layouts/settings-layout";
// hooks // hooks
import useUserAuth from "hooks/use-user-auth";
import useProjectDetails from "hooks/use-project-details";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// components // components
import { AutoArchiveAutomation, AutoCloseAutomation } from "components/automation"; import { AutoArchiveAutomation, AutoCloseAutomation } from "components/automation";
@ -16,45 +14,32 @@ import { ProjectSettingHeader } from "components/headers";
// types // types
import { NextPageWithLayout } from "types/app"; import { NextPageWithLayout } from "types/app";
import { IProject } from "types"; import { IProject } from "types";
// constant
import { USER_PROJECT_VIEW } from "constants/fetch-keys";
// services const AutomationSettingsPage: NextPageWithLayout = observer(() => {
const projectService = new ProjectService();
const projectMemberService = new ProjectMemberService();
const AutomationSettingsPage: NextPageWithLayout = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query; const { workspaceSlug, projectId } = router.query;
const { user } = useUserAuth();
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { projectDetails } = useProjectDetails(); // store
const {
const { data: memberDetails } = useSWR( user: { currentProjectRole },
workspaceSlug && projectId ? USER_PROJECT_VIEW(projectId.toString()) : null, project: { currentProjectDetails: projectDetails, updateProject },
workspaceSlug && projectId } = useMobxStore();
? () => projectMemberService.projectMemberMe(workspaceSlug.toString(), projectId.toString())
: null
);
const handleChange = async (formData: Partial<IProject>) => { const handleChange = async (formData: Partial<IProject>) => {
if (!workspaceSlug || !projectId || !projectDetails) return; if (!workspaceSlug || !projectId || !projectDetails) return;
await projectService await updateProject(workspaceSlug.toString(), projectId.toString(), formData).catch(() => {
.updateProject(workspaceSlug as string, projectId as string, formData, user) setToastAlert({
.then(() => {}) type: "error",
.catch(() => { title: "Error!",
setToastAlert({ message: "Something went wrong. Please try again.",
type: "error",
title: "Error!",
message: "Something went wrong. Please try again.",
});
}); });
});
}; };
const isAdmin = memberDetails?.role === 20; const isAdmin = currentProjectRole === 20;
return ( return (
<section className={`pr-9 py-8 w-full overflow-y-auto ${isAdmin ? "" : "opacity-60"}`}> <section className={`pr-9 py-8 w-full overflow-y-auto ${isAdmin ? "" : "opacity-60"}`}>
@ -65,7 +50,7 @@ const AutomationSettingsPage: NextPageWithLayout = () => {
<AutoCloseAutomation handleChange={handleChange} /> <AutoCloseAutomation handleChange={handleChange} />
</section> </section>
); );
}; });
AutomationSettingsPage.getLayout = function getLayout(page: ReactElement) { AutomationSettingsPage.getLayout = function getLayout(page: ReactElement) {
return ( return (

View File

@ -17,7 +17,7 @@ const EstimatesSettingsPage: NextPageWithLayout = () => (
EstimatesSettingsPage.getLayout = function getLayout(page: ReactElement) { EstimatesSettingsPage.getLayout = function getLayout(page: ReactElement) {
return ( return (
<AppLayout header={<ProjectSettingHeader title="Estimates Settings" />} withProjectWrapper> <AppLayout header={<ProjectSettingHeader title="Estimates Settings" />} withProjectWrapper>
<ProjectSettingLayout>{page}; </ProjectSettingLayout> <ProjectSettingLayout>{page}</ProjectSettingLayout>
</AppLayout> </AppLayout>
); );
}; };