feat: table view for mobiles

This commit is contained in:
2025-10-06 09:37:58 +04:00
parent 665625557d
commit b316cf4f7a
14 changed files with 142 additions and 106 deletions

View File

@ -6,9 +6,11 @@ import { useDealsContext } from "@/app/deals/contexts/DealsContext";
import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext"; import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext";
import BaseTable from "@/components/ui/BaseTable/BaseTable"; import BaseTable from "@/components/ui/BaseTable/BaseTable";
import { useDrawersContext } from "@/drawers/DrawersContext"; import { useDrawersContext } from "@/drawers/DrawersContext";
import useIsMobile from "@/hooks/utils/useIsMobile";
import { DealSchema } from "@/lib/client"; import { DealSchema } from "@/lib/client";
const DealsTable: FC = () => { const DealsTable: FC = () => {
const isMobile = useIsMobile();
const { selectedProject } = useProjectsContext(); const { selectedProject } = useProjectsContext();
const { deals, paginationInfo, page, setPage, sortingForm, dealsCrud } = const { deals, paginationInfo, page, setPage, sortingForm, dealsCrud } =
useDealsContext(); useDealsContext();
@ -33,9 +35,11 @@ const DealsTable: FC = () => {
return ( return (
<Stack <Stack
p={isMobile ? "xs" : ""}
gap={"xs"} gap={"xs"}
h={"100%"}> h={"100%"}>
<BaseTable <BaseTable
withTableBorder
records={[...deals]} records={[...deals]}
columns={columns} columns={columns}
sortStatus={{ sortStatus={{

View File

@ -1,7 +1,8 @@
import { useMemo } from "react"; import { useMemo } from "react";
import { IconEdit } from "@tabler/icons-react"; import { IconEdit } from "@tabler/icons-react";
import { DataTableColumn } from "mantine-datatable"; import { DataTableColumn } from "mantine-datatable";
import { ActionIcon, Tooltip } from "@mantine/core"; import ActionIconWithTip from "@/components/ui/ActionIconWithTip/ActionIconWithTip";
import useIsMobile from "@/hooks/utils/useIsMobile";
import { DealSchema } from "@/lib/client"; import { DealSchema } from "@/lib/client";
import { utcDateTimeToLocalString } from "@/utils/datetime"; import { utcDateTimeToLocalString } from "@/utils/datetime";
@ -10,44 +11,43 @@ type Props = {
}; };
const useDealsTableColumns = ({ onEditClick }: Props) => { const useDealsTableColumns = ({ onEditClick }: Props) => {
const isMobile = useIsMobile();
return useMemo( return useMemo(
() => () =>
[ [
{ {
accessor: "actions", accessor: "actions",
title: "Действия", title: isMobile ? "" : "Действия",
sortable: false, sortable: false,
textAlign: "center", textAlign: "center",
width: "0%", width: "0%",
render: deal => ( render: deal => (
<Tooltip label="Редактировать"> <ActionIconWithTip
<ActionIcon tipLabel={"Редактировать"}
bdrs={"md"}
size={"lg"}
onClick={() => onEditClick(deal)} onClick={() => onEditClick(deal)}
variant={"default"}> variant={isMobile ? "subtle" : "default"}>
<IconEdit /> <IconEdit />
</ActionIcon> </ActionIconWithTip>
</Tooltip>
), ),
}, },
{ {
accessor: "id", accessor: "id",
title: "Номер", title: isMobile ? "№" : "Номер",
sortable: true, sortable: true,
width: "30%", width: "20%",
}, },
{ {
accessor: "name", accessor: "name",
title: "Название", title: "Название",
width: "40%", width: "45%",
}, },
{ {
title: "Дата создания", title: "Дата создания",
accessor: "createdAt", accessor: "createdAt",
render: deal => utcDateTimeToLocalString(deal.createdAt), render: deal => utcDateTimeToLocalString(deal.createdAt),
sortable: true, sortable: true,
width: "30%", width: "35%",
}, },
] as DataTableColumn<DealSchema>[], ] as DataTableColumn<DealSchema>[],
[onEditClick] [onEditClick]

View File

@ -12,7 +12,11 @@ import { DealsFiltersForm } from "@/app/deals/hooks/useDealsFilters";
import SmallPageBlock from "@/components/layout/SmallPageBlock/SmallPageBlock"; import SmallPageBlock from "@/components/layout/SmallPageBlock/SmallPageBlock";
import useIsMobile from "@/hooks/utils/useIsMobile"; import useIsMobile from "@/hooks/utils/useIsMobile";
export type View = "board" | "table" | "schedule"; export enum View {
BOARD = "board",
TABLE = "table",
SCHEDULE = "schedule"
}
type Props = { type Props = {
view: View; view: View;
@ -42,7 +46,7 @@ const TopToolPanel: FC<Props> = ({ view, setView }) => {
onChange: (values: DealsFiltersForm) => onChange: (values: DealsFiltersForm) =>
dealsFiltersForm.setValues(values), dealsFiltersForm.setValues(values),
project: selectedProject, project: selectedProject,
boardAndStatusEnabled: view === "table", boardAndStatusEnabled: view === View.TABLE,
}, },
}); });
}; };

View File

@ -16,15 +16,15 @@ type Props = {
const ViewSelector: FC<Props> = ({ value, onChange }) => { const ViewSelector: FC<Props> = ({ value, onChange }) => {
const views = [ const views = [
{ {
value: "board" as View, value: View.BOARD,
icon: <IconLayoutDashboard />, icon: <IconLayoutDashboard />,
}, },
{ {
value: "table" as View, value: View.TABLE,
icon: <IconMenu2 />, icon: <IconMenu2 />,
}, },
{ {
value: "schedule" as View, value: View.SCHEDULE,
icon: <IconCalendarWeekFilled />, icon: <IconCalendarWeekFilled />,
}, },
]; ];

View File

@ -1,9 +1,7 @@
"use client"; "use client";
import { useState } from "react"; import { Box } from "@mantine/core";
import TopToolPanel, { import TopToolPanel from "@/app/deals/components/desktop/TopToolPanel/TopToolPanel";
View,
} from "@/app/deals/components/desktop/TopToolPanel/TopToolPanel";
import { import {
BoardView, BoardView,
ScheduleView, ScheduleView,
@ -12,11 +10,14 @@ import {
import { useBoardsContext } from "@/app/deals/contexts/BoardsContext"; import { useBoardsContext } from "@/app/deals/contexts/BoardsContext";
import { DealsContextProvider } from "@/app/deals/contexts/DealsContext"; import { DealsContextProvider } from "@/app/deals/contexts/DealsContext";
import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext"; import { useProjectsContext } from "@/app/deals/contexts/ProjectsContext";
import useView from "@/app/deals/hooks/useView";
import PageBlock from "@/components/layout/PageBlock/PageBlock";
const PageBody = () => { const PageBody = () => {
const { selectedBoard } = useBoardsContext(); const { selectedBoard } = useBoardsContext();
const { selectedProject } = useProjectsContext(); const { selectedProject } = useProjectsContext();
const [view, setView] = useState<View>("board");
const { view, setView } = useView();
const getViewContent = () => { const getViewContent = () => {
switch (view) { switch (view) {
@ -42,7 +43,11 @@ const PageBody = () => {
view={view} view={view}
setView={setView} setView={setView}
/> />
{getViewContent()} <PageBlock
fullScreenMobile
style={{ flex: 1 }}>
<Box h={"100%"}>{getViewContent()}</Box>
</PageBlock>
</DealsContextProvider> </DealsContextProvider>
); );
}; };

View File

@ -1,12 +1,11 @@
import { Space } from "@mantine/core"; import { Space } from "@mantine/core";
import MainBlockHeader from "@/app/deals/components/mobile/MainBlockHeader/MainBlockHeader"; import MainBlockHeader from "@/app/deals/components/mobile/MainBlockHeader/MainBlockHeader";
import Funnel from "@/app/deals/components/shared/Funnel/Funnel"; import Funnel from "@/app/deals/components/shared/Funnel/Funnel";
import PageBlock from "@/components/layout/PageBlock/PageBlock";
export const BoardView = () => ( export const BoardView = () => (
<PageBlock style={{ height: "100%" }}> <>
<MainBlockHeader /> <MainBlockHeader />
<Space h="md" /> <Space h="md" />
<Funnel /> <Funnel />
</PageBlock> </>
); );

View File

@ -1,5 +1,3 @@
import PageBlock from "@/components/layout/PageBlock/PageBlock";
export const ScheduleView = () => { export const ScheduleView = () => {
return <PageBlock>-</PageBlock>; return <>-</>;
}; };

View File

@ -1,8 +1,3 @@
import DealsTable from "@/app/deals/components/desktop/DealsTable/DealsTable"; import DealsTable from "@/app/deals/components/desktop/DealsTable/DealsTable";
import PageBlock from "@/components/layout/PageBlock/PageBlock";
export const TableView = () => ( export const TableView = () => <DealsTable />;
<PageBlock style={{ height: "100%" }}>
<DealsTable />
</PageBlock>
);

View File

@ -0,0 +1,34 @@
import { useEffect, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { View } from "@/app/deals/components/desktop/TopToolPanel/TopToolPanel";
const useView = () => {
const router = useRouter();
const searchParams = useSearchParams();
const viewsArray = Object.values(View);
const [view, setView] = useState<View>(() => {
const urlView = searchParams.get("view") as View;
return urlView && viewsArray.includes(urlView) ? urlView : View.BOARD;
});
useEffect(() => {
const urlView = searchParams.get("view") as View;
if (urlView && viewsArray.includes(urlView)) {
setView(urlView);
}
}, [searchParams]);
useEffect(() => {
const params = new URLSearchParams(searchParams.toString());
if (params.get("view") === view) return;
router.replace(`?view=${view}`);
}, [view]);
return {
view,
setView,
};
};
export default useView;

View File

@ -1,33 +1,29 @@
import { IconCircleDotted, IconLayoutKanban } from "@tabler/icons-react"; import { Group, Stack, Text } from "@mantine/core";
import FooterButtons, { import FooterClickable from "@/components/layout/Footer/components/FooterClickable";
LinkData, import buttonsData from "@/components/layout/Footer/data/buttonsData";
} from "@/components/layout/Footer/FooterButtons"; import styles from "./Footer.module.css";
const buttonsData: LinkData[] = [
{
icon: IconLayoutKanban,
label: "Сделки",
href: "/deals",
},
{
icon: IconCircleDotted,
label: "Label 1",
href: "/oiiai",
},
{
icon: IconCircleDotted,
label: "Label 2",
href: "/opaopa",
},
{
icon: IconCircleDotted,
label: "Label 3",
href: "/hihihaha",
},
];
const Footer = () => { const Footer = () => {
return <FooterButtons buttonsData={buttonsData} />; return (
<Group
className={styles.container}
p={"xs"}
wrap={"nowrap"}
justify={"space-between"}>
{buttonsData.map(data => (
<FooterClickable
href={data.href}
key={data.label}>
<Stack
gap={0}
align={"center"}>
<data.icon />
<Text>{data.label}</Text>
</Stack>
</FooterClickable>
))}
</Group>
);
}; };
export default Footer; export default Footer;

View File

@ -1,39 +0,0 @@
import { IconPlus } from "@tabler/icons-react";
import { Group, Stack, Text } from "@mantine/core";
import FooterClickable from "@/components/layout/Footer/FooterClickable";
import styles from "./Footer.module.css";
export type LinkData = {
icon: typeof IconPlus;
label: string;
href: string;
};
type Props = {
buttonsData: LinkData[];
};
const FooterButtons = ({ buttonsData }: Props) => {
return (
<Group
className={styles.container}
p={"xs"}
wrap={"nowrap"}
justify={"space-between"}>
{buttonsData.map(data => (
<FooterClickable
href={data.href}
key={data.label}>
<Stack
gap={0}
align={"center"}>
<data.icon />
<Text>{data.label}</Text>
</Stack>
</FooterClickable>
))}
</Group>
);
};
export default FooterButtons;

View File

@ -3,7 +3,7 @@
import { ReactNode } from "react"; import { ReactNode } from "react";
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import styles from "./Footer.module.css"; import styles from "../Footer.module.css";
type Props = { type Props = {
href: string; href: string;

View File

@ -0,0 +1,31 @@
import {
IconCircleDotted,
IconLayoutKanban,
IconTable,
} from "@tabler/icons-react";
import LinkData from "@/components/layout/Navbar/types/LinkData";
const buttonsData: LinkData[] = [
{
icon: IconLayoutKanban,
label: "Воронка",
href: "/deals?view=board",
},
{
icon: IconTable,
label: "Таблица",
href: "/deals?view=table",
},
{
icon: IconCircleDotted,
label: "Label 2",
href: "/opaopa",
},
{
icon: IconCircleDotted,
label: "Label 3",
href: "/hihihaha",
},
];
export default buttonsData;

View File

@ -0,0 +1,9 @@
import { IconPlus } from "@tabler/icons-react";
type LinkData = {
icon: typeof IconPlus;
label: string;
href: string;
};
export default LinkData;