chore: global issues ui improvement and bug fixes (#2300)

This commit is contained in:
Anmol Singh Bhatia 2023-09-29 12:36:38 +05:30 committed by GitHub
parent 6cb4b222d0
commit 459999e8c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 99 additions and 83 deletions

View File

@ -82,7 +82,7 @@ export const IssueColumn: React.FC<Props> = ({
const isNotAllowed = userAuth.isGuest || userAuth.isViewer; const isNotAllowed = userAuth.isGuest || userAuth.isViewer;
return ( return (
<div className="group flex items-center w-[28rem] text-sm h-11 sticky top-0 bg-custom-background-100 truncate border-b border-r border-custom-border-200"> <div className="group flex items-center w-[28rem] text-sm h-11 sticky top-0 bg-custom-background-100 truncate border-b border-r border-custom-border-100">
<div <div
className="flex gap-1.5 px-4 pr-0 py-2.5 items-center w-24" className="flex gap-1.5 px-4 pr-0 py-2.5 items-center w-24"
style={issue.parent && nestingLevel !== 0 ? { paddingLeft } : {}} style={issue.parent && nestingLevel !== 0 ? { paddingLeft } : {}}
@ -101,7 +101,7 @@ export const IssueColumn: React.FC<Props> = ({
onInteraction={(nextOpenState) => setIsOpen(nextOpenState)} onInteraction={(nextOpenState) => setIsOpen(nextOpenState)}
content={ content={
<div <div
className={`flex flex-col gap-1.5 overflow-y-scroll whitespace-nowrap rounded-md border p-1 text-xs shadow-lg focus:outline-none max-h-44 min-w-full border-custom-border-200 bg-custom-background-90`} className={`flex flex-col gap-1.5 overflow-y-scroll whitespace-nowrap rounded-md border p-1 text-xs shadow-lg focus:outline-none max-h-44 min-w-full border-custom-border-100 bg-custom-background-90`}
> >
<button <button
type="button" type="button"

View File

@ -239,7 +239,7 @@ export const SpreadsheetView: React.FC<Props> = ({
descendingOrder: TIssueOrderByOptions descendingOrder: TIssueOrderByOptions
) => ( ) => (
<div className="relative flex flex-col h-max w-full bg-custom-background-100"> <div className="relative flex flex-col h-max w-full bg-custom-background-100">
<div className="flex items-center min-w-[9rem] px-4 py-2.5 text-sm font-medium z-[1] h-11 w-full sticky top-0 bg-custom-background-90 border border-l-0 border-custom-border-200"> <div className="flex items-center min-w-[9rem] px-4 py-2.5 text-sm font-medium z-[1] h-11 w-full sticky top-0 bg-custom-background-90 border border-l-0 border-custom-border-100">
<CustomMenu <CustomMenu
customButtonClassName="!w-full" customButtonClassName="!w-full"
className="!w-full" className="!w-full"
@ -491,7 +491,7 @@ export const SpreadsheetView: React.FC<Props> = ({
isScrolled ? "shadow-r shadow-custom-shadow-xs" : "" isScrolled ? "shadow-r shadow-custom-shadow-xs" : ""
}`} }`}
> >
<div className="flex items-center text-sm font-medium z-[2] h-11 w-full sticky top-0 bg-custom-background-90 border border-l-0 border-custom-border-200"> <div className="flex items-center text-sm font-medium z-[2] h-11 w-full sticky top-0 bg-custom-background-90 border border-l-0 border-custom-border-100">
<span className="flex items-center px-4 py-2.5 h-full w-24 flex-shrink-0"> <span className="flex items-center px-4 py-2.5 h-full w-24 flex-shrink-0">
ID ID
</span> </span>

View File

@ -31,7 +31,7 @@ import { IIssue, IWorkspaceIssueFilterOptions } from "types";
export const WorkspaceViewIssues = () => { export const WorkspaceViewIssues = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, workspaceViewId } = router.query; const { workspaceSlug, viewId } = router.query;
const { memberRole } = useProjectMyMembership(); const { memberRole } = useProjectMyMembership();
const { user } = useUser(); const { user } = useUser();
@ -189,7 +189,7 @@ export const WorkspaceViewIssues = () => {
/> />
<PrimaryButton <PrimaryButton
onClick={() => { onClick={() => {
if (workspaceViewId) handleFilters("filters", filters.filters, true); if (viewId) handleFilters("filters", filters.filters, true);
else else
setCreateViewModal({ setCreateViewModal({
query: filters.filters, query: filters.filters,
@ -197,8 +197,8 @@ export const WorkspaceViewIssues = () => {
}} }}
className="flex items-center gap-2 text-sm" className="flex items-center gap-2 text-sm"
> >
{!workspaceViewId && <PlusIcon className="h-4 w-4" />} {!viewId && <PlusIcon className="h-4 w-4" />}
{workspaceViewId ? "Update" : "Save"} view {viewId ? "Update" : "Save"} view
</PrimaryButton> </PrimaryButton>
</div> </div>
{<div className="mt-3 border-t border-custom-border-200" />} {<div className="mt-3 border-t border-custom-border-200" />}

View File

@ -31,7 +31,7 @@ import { IIssue, IWorkspaceIssueFilterOptions } from "types";
export const WorkspaceAllIssue = () => { export const WorkspaceAllIssue = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, workspaceViewId } = router.query; const { workspaceSlug, viewId } = router.query;
const [createViewModal, setCreateViewModal] = useState<any>(null); const [createViewModal, setCreateViewModal] = useState<any>(null);
@ -203,7 +203,7 @@ export const WorkspaceAllIssue = () => {
/> />
<PrimaryButton <PrimaryButton
onClick={() => { onClick={() => {
if (workspaceViewId) handleFilters("filters", filters.filters, true); if (viewId) handleFilters("filters", filters.filters, true);
else else
setCreateViewModal({ setCreateViewModal({
query: filters.filters, query: filters.filters,
@ -211,8 +211,8 @@ export const WorkspaceAllIssue = () => {
}} }}
className="flex items-center gap-2 text-sm" className="flex items-center gap-2 text-sm"
> >
{!workspaceViewId && <PlusIcon className="h-4 w-4" />} {!viewId && <PlusIcon className="h-4 w-4" />}
{workspaceViewId ? "Update" : "Save"} view {viewId ? "Update" : "Save"} view
</PrimaryButton> </PrimaryButton>
</div> </div>
{<div className="mt-3 border-t border-custom-border-200" />} {<div className="mt-3 border-t border-custom-border-200" />}

View File

@ -91,7 +91,7 @@ export const SingleViewItem: React.FC<Props> = ({
const viewRedirectionUrl = const viewRedirectionUrl =
viewType === "project" viewType === "project"
? `/${workspaceSlug}/projects/${projectId}/views/${view.id}` ? `/${workspaceSlug}/projects/${projectId}/views/${view.id}`
: `/${workspaceSlug}/workspace-views/${view.id}`; : `/${workspaceSlug}/workspace-views/issues?viewId=${view.id}`;
return ( return (
<div className="group hover:bg-custom-background-90 border-b border-custom-border-200"> <div className="group hover:bg-custom-background-90 border-b border-custom-border-200">

View File

@ -34,7 +34,7 @@ const workspaceLinks = (workspaceSlug: string) => [
}, },
{ {
Icon: TaskAltOutlined, Icon: TaskAltOutlined,
name: "Issues", name: "All Issues",
href: `/${workspaceSlug}/workspace-views/all-issues`, href: `/${workspaceSlug}/workspace-views/all-issues`,
}, },
]; ];

View File

@ -48,10 +48,12 @@ export const CreateUpdateWorkspaceViewModal: React.FC<Props> = ({
}; };
await workspaceService await workspaceService
.createView(workspaceSlug as string, payloadData) .createView(workspaceSlug as string, payloadData)
.then(() => { .then((res) => {
mutate(WORKSPACE_VIEWS_LIST(workspaceSlug as string)); mutate(WORKSPACE_VIEWS_LIST(workspaceSlug as string));
handleClose(); handleClose();
router.replace(`/${workspaceSlug}/workspace-views/issues?viewId=${res.id}`);
setToastAlert({ setToastAlert({
type: "success", type: "success",
title: "Success!", title: "Success!",

View File

@ -17,7 +17,7 @@ type Props = {
export const WorkspaceViewsNavigation: React.FC<Props> = ({ handleAddView }) => { export const WorkspaceViewsNavigation: React.FC<Props> = ({ handleAddView }) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, workspaceViewId } = router.query; const { workspaceSlug, viewId } = router.query;
const { data: workspaceViews } = useSWR( const { data: workspaceViews } = useSWR(
workspaceSlug ? WORKSPACE_VIEWS_LIST(workspaceSlug.toString()) : null, workspaceSlug ? WORKSPACE_VIEWS_LIST(workspaceSlug.toString()) : null,
@ -25,67 +25,80 @@ export const WorkspaceViewsNavigation: React.FC<Props> = ({ handleAddView }) =>
); );
const isSelected = (pathName: string) => router.pathname.includes(pathName); const isSelected = (pathName: string) => router.pathname.includes(pathName);
React.useEffect(() => {
const activeTabElement = document.getElementById("active-tab-global-view");
if (activeTabElement) activeTabElement.scrollIntoView({ behavior: "smooth", inline: "center" });
}, [viewId, workspaceViews]);
const tabsList = [ const tabsList = [
{ {
key: "all", key: "all",
label: "All Issues", label: "All Issues",
selected: isSelected("workspace-views/all-issues"), selected: isSelected("workspace-views/all-issues"),
onClick: () => router.push(`/${workspaceSlug}/workspace-views/all-issues`), onClick: () => router.replace(`/${workspaceSlug}/workspace-views/all-issues`),
}, },
{ {
key: "assigned", key: "assigned",
label: "Assigned", label: "Assigned",
selected: isSelected("workspace-views/assigned"), selected: isSelected("workspace-views/assigned"),
onClick: () => router.push(`/${workspaceSlug}/workspace-views/assigned`), onClick: () => router.replace(`/${workspaceSlug}/workspace-views/assigned`),
}, },
{ {
key: "created", key: "created",
label: "Created", label: "Created",
selected: isSelected("workspace-views/created"), selected: isSelected("workspace-views/created"),
onClick: () => router.push(`/${workspaceSlug}/workspace-views/created`), onClick: () => router.replace(`/${workspaceSlug}/workspace-views/created`),
}, },
{ {
key: "subscribed", key: "subscribed",
label: "Subscribed", label: "Subscribed",
selected: isSelected("workspace-views/subscribed"), selected: isSelected("workspace-views/subscribed"),
onClick: () => router.push(`/${workspaceSlug}/workspace-views/subscribed`), onClick: () => router.replace(`/${workspaceSlug}/workspace-views/subscribed`),
}, },
]; ];
return ( return (
<div className="group flex items-center overflow-x-scroll"> <div className="group flex items-center gap-x-1 overflow-x-scroll relative">
{tabsList.map((tab) => ( {tabsList.map((tab) => (
<button <button
key={tab.key} key={tab.key}
type="button" type="button"
onClick={tab.onClick} onClick={tab.onClick}
className={`border-b-2 min-w-[96px] p-4 text-sm font-medium outline-none whitespace-nowrap ${ className={`border-b-2 min-w-min p-3 text-sm font-medium outline-none whitespace-nowrap flex-shrink-0 ${
tab.selected tab.selected
? "border-custom-primary-100 text-custom-primary-100" ? "border-custom-primary-100 text-custom-primary-100"
: "border-transparent hover:border-custom-primary-100 hover:text-custom-primary-100" : "border-transparent hover:border-custom-border-200 hover:text-custom-text-400"
}`} }`}
id={tab.selected ? `active-tab-global-view` : ``}
> >
{tab.label} {tab.label}
</button> </button>
))} ))}
{workspaceViews && {workspaceViews &&
workspaceViews.length > 0 && workspaceViews.length > 0 &&
workspaceViews?.map((view) => ( workspaceViews?.map((view) => (
<button <button
className={`border-b-2 min-w-[96px] p-4 text-sm font-medium outline-none whitespace-nowrap ${ className={`border-b-2 min-w-min p-3 text-sm font-medium outline-none whitespace-nowrap flex-shrink-0 ${
view.id === workspaceViewId view.id === viewId
? "border-custom-primary-100 text-custom-primary-100" ? "border-custom-primary-100 text-custom-primary-100"
: "border-transparent hover:border-custom-primary-100 hover:text-custom-primary-100" : "border-transparent hover:border-custom-border-200 hover:text-custom-text-400"
}`} }`}
onClick={() => router.push(`/${workspaceSlug}/workspace-views/${view.id}`)} id={view.id === viewId ? `active-tab-global-view` : ``}
onClick={() =>
router.replace(`/${workspaceSlug}/workspace-views/issues?viewId=${view.id}`)
}
> >
{view.name} {view.name}
</button> </button>
))} ))}
<button type="button" className="min-w-[96px] " onClick={handleAddView}> <button
<PlusIcon className="h-4 w-4 text-custom-primary-200 hover:text-current" /> type="button"
className="flex items-center justify-center flex-shrink-0 sticky right-0 w-12 py-3 border-transparent bg-custom-background-100 hover:border-custom-border-200 hover:text-custom-text-400"
onClick={handleAddView}
>
<PlusIcon className="h-4 w-4 text-custom-primary-200" />
</button> </button>
</div> </div>
); );

View File

@ -67,19 +67,19 @@ export const initialState: IWorkspaceGlobalViewProps = {
const saveViewFilters = async ( const saveViewFilters = async (
workspaceSlug: string, workspaceSlug: string,
workspaceViewId: string, viewId: string,
state: IWorkspaceGlobalViewProps state: IWorkspaceGlobalViewProps
) => { ) => {
await workspaceService.updateView(workspaceSlug, workspaceViewId, { await workspaceService.updateView(workspaceSlug, viewId, {
query_data: state, query_data: state,
}); });
}; };
export const WorkspaceViewProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { export const WorkspaceViewProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, workspaceViewId } = router.query as { const { workspaceSlug, viewId } = router.query as {
workspaceSlug: string; workspaceSlug: string;
workspaceViewId: string; viewId: string;
}; };
const [filters, setFilters] = useState<IWorkspaceGlobalViewProps>(initialState); const [filters, setFilters] = useState<IWorkspaceGlobalViewProps>(initialState);
@ -98,7 +98,7 @@ export const WorkspaceViewProvider: React.FC<{ children: React.ReactNode }> = ({
}; };
setFilters(() => updatedFilterPayload); setFilters(() => updatedFilterPayload);
if (saveFiltersToServer) saveViewFilters(workspaceSlug, workspaceViewId, updatedFilterPayload); if (saveFiltersToServer) saveViewFilters(workspaceSlug, viewId, updatedFilterPayload);
}; };
const computedFilter = (filters: any) => { const computedFilter = (filters: any) => {
@ -151,9 +151,9 @@ export const WorkspaceViewProvider: React.FC<{ children: React.ReactNode }> = ({
}; };
const { data: view, isLoading: viewLoading } = useSWR( const { data: view, isLoading: viewLoading } = useSWR(
workspaceSlug && workspaceViewId ? WORKSPACE_VIEW_DETAILS(workspaceViewId.toString()) : null, workspaceSlug && viewId ? WORKSPACE_VIEW_DETAILS(viewId.toString()) : null,
workspaceSlug && workspaceViewId workspaceSlug && viewId
? () => workspaceService.getViewDetails(workspaceSlug.toString(), workspaceViewId.toString()) ? () => workspaceService.getViewDetails(workspaceSlug.toString(), viewId.toString())
: null : null
); );
@ -162,10 +162,10 @@ export const WorkspaceViewProvider: React.FC<{ children: React.ReactNode }> = ({
mutate: mutateViewIssues, mutate: mutateViewIssues,
isLoading: viewIssueLoading, isLoading: viewIssueLoading,
} = useSWR( } = useSWR(
workspaceSlug && view && workspaceViewId && filters workspaceSlug && view && viewId && filters
? WORKSPACE_VIEW_ISSUES(workspaceViewId.toString(), params) ? WORKSPACE_VIEW_ISSUES(viewId.toString(), params)
: null, : null,
workspaceSlug && view && workspaceViewId workspaceSlug && view && viewId
? () => ? () =>
workspaceService.getViewIssues( workspaceService.getViewIssues(
workspaceSlug.toString(), workspaceSlug.toString(),

View File

@ -312,51 +312,52 @@ const WorkspaceSettings: NextPage = () => {
</PrimaryButton> </PrimaryButton>
</div> </div>
</div> </div>
{isAdmin && (
<Disclosure as="div" className="border-t border-custom-border-400">
{({ open }) => (
<div className="w-full">
<Disclosure.Button
as="button"
type="button"
className="flex items-center justify-between w-full py-4"
>
<span className="text-xl tracking-tight">Delete Workspace</span>
<Icon iconName={open ? "expand_less" : "expand_more"} className="!text-2xl" />
</Disclosure.Button>
<Disclosure as="div" className="border-t border-custom-border-400"> <Transition
{({ open }) => ( show={open}
<div className="w-full"> enter="transition duration-100 ease-out"
<Disclosure.Button enterFrom="transform opacity-0"
as="button" enterTo="transform opacity-100"
type="button" leave="transition duration-75 ease-out"
className="flex items-center justify-between w-full py-4" leaveFrom="transform opacity-100"
> leaveTo="transform opacity-0"
<span className="text-xl tracking-tight">Delete Workspace</span> >
<Icon iconName={open ? "expand_less" : "expand_more"} className="!text-2xl" /> <Disclosure.Panel>
</Disclosure.Button> <div className="flex flex-col gap-8">
<span className="text-sm tracking-tight">
<Transition The danger zone of the workspace delete page is a critical area that
show={open} requires careful consideration and attention. When deleting a workspace,
enter="transition duration-100 ease-out" all of the data and resources within that workspace will be permanently
enterFrom="transform opacity-0" removed and cannot be recovered.
enterTo="transform opacity-100" </span>
leave="transition duration-75 ease-out" <div>
leaveFrom="transform opacity-100" <DangerButton
leaveTo="transform opacity-0" onClick={() => setIsOpen(true)}
> className="!text-sm"
<Disclosure.Panel> outline
<div className="flex flex-col gap-8"> >
<span className="text-sm tracking-tight"> Delete my workspace
The danger zone of the workspace delete page is a critical area that </DangerButton>
requires careful consideration and attention. When deleting a workspace, </div>
all of the data and resources within that workspace will be permanently
removed and cannot be recovered.
</span>
<div>
<DangerButton
onClick={() => setIsOpen(true)}
className="!text-sm"
outline
>
Delete my workspace
</DangerButton>
</div> </div>
</div> </Disclosure.Panel>
</Disclosure.Panel> </Transition>
</Transition> </div>
</div> )}
)} </Disclosure>
</Disclosure> )}
</div> </div>
) : ( ) : (
<div className="flex items-center justify-center h-full w-full px-4 sm:px-0"> <div className="flex items-center justify-center h-full w-full px-4 sm:px-0">