mirror of
https://github.com/umami-software/umami.git
synced 2025-12-06 01:18:00 +01:00
Added DialogButton to handle mobile.
This commit is contained in:
parent
036748cdeb
commit
40492ec7c4
32 changed files with 2146 additions and 1807 deletions
36
package.json
36
package.json
|
|
@ -77,12 +77,12 @@
|
|||
"@prisma/extension-read-replicas": "^0.4.1",
|
||||
"@react-spring/web": "^10.0.3",
|
||||
"@svgr/cli": "^8.1.0",
|
||||
"@tanstack/react-query": "^5.90.2",
|
||||
"@umami/react-zen": "^0.198.0",
|
||||
"@tanstack/react-query": "^5.90.5",
|
||||
"@umami/react-zen": "^0.200.0",
|
||||
"@umami/redis-client": "^0.29.0",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"chalk": "^5.6.2",
|
||||
"chart.js": "^4.5.0",
|
||||
"chart.js": "^4.5.1",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"classnames": "^2.3.1",
|
||||
"colord": "^2.9.2",
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
"del": "^6.0.0",
|
||||
"detect-browser": "^5.2.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"esbuild": "^0.25.10",
|
||||
"esbuild": "^0.25.11",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"fs-extra": "^11.3.2",
|
||||
"immer": "^10.1.3",
|
||||
|
|
@ -115,25 +115,25 @@
|
|||
"pg": "^8.16.3",
|
||||
"prisma": "6.16.3",
|
||||
"pure-rand": "^7.0.1",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-error-boundary": "^4.0.4",
|
||||
"react-intl": "^7.1.11",
|
||||
"react-intl": "^7.1.14",
|
||||
"react-simple-maps": "^2.3.0",
|
||||
"react-use-measure": "^2.0.4",
|
||||
"react-window": "^1.8.6",
|
||||
"request-ip": "^3.3.0",
|
||||
"semver": "^7.5.4",
|
||||
"semver": "^7.7.3",
|
||||
"serialize-error": "^12.0.0",
|
||||
"thenby": "^1.3.4",
|
||||
"ua-parser-js": "^2.0.5",
|
||||
"ua-parser-js": "^2.0.6",
|
||||
"uuid": "^11.1.0",
|
||||
"zod": "^4.1.11",
|
||||
"zod": "^4.1.12",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formatjs/cli": "^4.2.29",
|
||||
"@netlify/plugin-nextjs": "^5.13.3",
|
||||
"@netlify/plugin-nextjs": "^5.14.0",
|
||||
"@rollup/plugin-alias": "^5.0.0",
|
||||
"@rollup/plugin-commonjs": "^25.0.4",
|
||||
"@rollup/plugin-json": "^6.0.0",
|
||||
|
|
@ -142,12 +142,12 @@
|
|||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-typescript": "^12.1.4",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^24.6.0",
|
||||
"@types/react": "^19.1.16",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"@types/node": "^24.8.1",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@typescript-eslint/eslint-plugin": "^8.45.0",
|
||||
"@typescript-eslint/parser": "^8.45.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.1",
|
||||
"@typescript-eslint/parser": "^8.46.1",
|
||||
"babel-plugin-react-compiler": "19.1.0-rc.2",
|
||||
"cross-env": "^10.1.0",
|
||||
"cypress": "^13.6.6",
|
||||
|
|
@ -163,14 +163,14 @@
|
|||
"extract-react-intl-messages": "^4.1.1",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^29.7.0",
|
||||
"lint-staged": "^16.2.3",
|
||||
"lint-staged": "^16.2.4",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-flexbugs-fixes": "^5.0.2",
|
||||
"postcss-import": "^15.1.0",
|
||||
"postcss-preset-env": "7.8.3",
|
||||
"prettier": "^3.6.2",
|
||||
"prompts": "2.4.2",
|
||||
"rollup": "^4.52.3",
|
||||
"rollup": "^4.52.4",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-delete": "^3.0.1",
|
||||
"rollup-plugin-dts": "^6.2.3",
|
||||
|
|
|
|||
3260
pnpm-lock.yaml
generated
3260
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,24 +1,19 @@
|
|||
import { useMessages } from '@/components/hooks';
|
||||
import { Button, Icon, Modal, Dialog, DialogTrigger, Text } from '@umami/react-zen';
|
||||
import { Plus } from '@/components/icons';
|
||||
import { LinkEditForm } from './LinkEditForm';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
|
||||
export function LinkAddButton({ teamId }: { teamId?: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<Button data-test="button-website-add" variant="primary">
|
||||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.addLink)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.addLink)} style={{ width: 600 }}>
|
||||
{({ close }) => <LinkEditForm teamId={teamId} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</DialogTrigger>
|
||||
<DialogButton
|
||||
icon={<Plus />}
|
||||
label={formatMessage(labels.addLink)}
|
||||
variant="primary"
|
||||
width="600px"
|
||||
>
|
||||
{({ close }) => <LinkEditForm teamId={teamId} onClose={close} />}
|
||||
</DialogButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { Dialog } from '@umami/react-zen';
|
||||
import { ActionButton } from '@/components/input/ActionButton';
|
||||
import { Trash } from '@/components/icons';
|
||||
import { ConfirmationForm } from '@/components/common/ConfirmationForm';
|
||||
import { messages } from '@/components/messages';
|
||||
import { useDeleteQuery, useMessages } from '@/components/hooks';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
|
||||
export function LinkDeleteButton({
|
||||
linkId,
|
||||
|
|
@ -29,27 +28,30 @@ export function LinkDeleteButton({
|
|||
};
|
||||
|
||||
return (
|
||||
<ActionButton title={formatMessage(labels.delete)} icon={<Trash />}>
|
||||
<Dialog title={formatMessage(labels.confirm)} style={{ width: 400 }}>
|
||||
{({ close }) => (
|
||||
<ConfirmationForm
|
||||
message={
|
||||
<FormattedMessage
|
||||
{...messages.confirmRemove}
|
||||
values={{
|
||||
target: <b>{name}</b>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
isLoading={isPending}
|
||||
error={getErrorMessage(error)}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.delete)}
|
||||
buttonVariant="danger"
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</ActionButton>
|
||||
<DialogButton
|
||||
icon={<Trash />}
|
||||
title={formatMessage(labels.confirm)}
|
||||
variant="quiet"
|
||||
width="400px"
|
||||
>
|
||||
{({ close }) => (
|
||||
<ConfirmationForm
|
||||
message={
|
||||
<FormattedMessage
|
||||
{...messages.confirmRemove}
|
||||
values={{
|
||||
target: <b>{name}</b>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
isLoading={isPending}
|
||||
error={getErrorMessage(error)}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.delete)}
|
||||
buttonVariant="danger"
|
||||
/>
|
||||
)}
|
||||
</DialogButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,16 @@
|
|||
import { ActionButton } from '@/components/input/ActionButton';
|
||||
import { Edit } from '@/components/icons';
|
||||
import { Dialog } from '@umami/react-zen';
|
||||
import { LinkEditForm } from './LinkEditForm';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
|
||||
export function LinkEditButton({ linkId }: { linkId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<ActionButton title={formatMessage(labels.edit)} icon={<Edit />}>
|
||||
<Dialog title={formatMessage(labels.link)} style={{ width: 800, minHeight: 300 }}>
|
||||
{({ close }) => {
|
||||
return <LinkEditForm linkId={linkId} onClose={close} />;
|
||||
}}
|
||||
</Dialog>
|
||||
</ActionButton>
|
||||
<DialogButton icon={<Edit />} title={formatMessage(labels.link)} variant="quiet" width="800px">
|
||||
{({ close }) => {
|
||||
return <LinkEditForm linkId={linkId} onClose={close} />;
|
||||
}}
|
||||
</DialogButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,19 @@
|
|||
import { useMessages } from '@/components/hooks';
|
||||
import { Button, Icon, Modal, Dialog, DialogTrigger, Text } from '@umami/react-zen';
|
||||
import { Plus } from '@/components/icons';
|
||||
import { PixelEditForm } from './PixelEditForm';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
|
||||
export function PixelAddButton({ teamId }: { teamId?: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<Button data-test="button-website-add" variant="primary">
|
||||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.addPixel)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.addPixel)} style={{ width: 600 }}>
|
||||
{({ close }) => <PixelEditForm teamId={teamId} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</DialogTrigger>
|
||||
<DialogButton
|
||||
icon={<Plus />}
|
||||
label={formatMessage(labels.addPixel)}
|
||||
variant="primary"
|
||||
width="600px"
|
||||
>
|
||||
{({ close }) => <PixelEditForm teamId={teamId} onClose={close} />}
|
||||
</DialogButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { Dialog } from '@umami/react-zen';
|
||||
import { ActionButton } from '@/components/input/ActionButton';
|
||||
import { Trash } from '@/components/icons';
|
||||
import { ConfirmationForm } from '@/components/common/ConfirmationForm';
|
||||
import { messages } from '@/components/messages';
|
||||
import { useDeleteQuery, useMessages, useModified } from '@/components/hooks';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
|
||||
export function PixelDeleteButton({
|
||||
pixelId,
|
||||
name,
|
||||
|
|
@ -28,27 +28,30 @@ export function PixelDeleteButton({
|
|||
};
|
||||
|
||||
return (
|
||||
<ActionButton title={formatMessage(labels.delete)} icon={<Trash />}>
|
||||
<Dialog title={formatMessage(labels.confirm)} style={{ width: 400 }}>
|
||||
{({ close }) => (
|
||||
<ConfirmationForm
|
||||
message={
|
||||
<FormattedMessage
|
||||
{...messages.confirmRemove}
|
||||
values={{
|
||||
target: <b>{name}</b>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
isLoading={isPending}
|
||||
error={getErrorMessage(error)}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.delete)}
|
||||
buttonVariant="danger"
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</ActionButton>
|
||||
<DialogButton
|
||||
icon={<Trash />}
|
||||
variant="quiet"
|
||||
title={formatMessage(labels.confirm)}
|
||||
width="400px"
|
||||
>
|
||||
{({ close }) => (
|
||||
<ConfirmationForm
|
||||
message={
|
||||
<FormattedMessage
|
||||
{...messages.confirmRemove}
|
||||
values={{
|
||||
target: <b>{name}</b>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
isLoading={isPending}
|
||||
error={getErrorMessage(error)}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.delete)}
|
||||
buttonVariant="danger"
|
||||
/>
|
||||
)}
|
||||
</DialogButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
import { ActionButton } from '@/components/input/ActionButton';
|
||||
import { Edit } from '@/components/icons';
|
||||
import { Dialog } from '@umami/react-zen';
|
||||
import { PixelEditForm } from './PixelEditForm';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
|
||||
export function PixelEditButton({ pixelId }: { pixelId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<ActionButton title={formatMessage(labels.edit)} icon={<Edit />}>
|
||||
<Dialog title={formatMessage(labels.pixel)} style={{ width: 600, minHeight: 300 }}>
|
||||
{({ close }) => {
|
||||
return <PixelEditForm pixelId={pixelId} onClose={close} />;
|
||||
}}
|
||||
</Dialog>
|
||||
</ActionButton>
|
||||
<DialogButton
|
||||
icon={<Edit />}
|
||||
title={formatMessage(labels.addPixel)}
|
||||
variant="quiet"
|
||||
width="600px"
|
||||
>
|
||||
{({ close }) => {
|
||||
return <PixelEditForm pixelId={pixelId} onClose={close} />;
|
||||
}}
|
||||
</DialogButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useMessages, useModified } from '@/components/hooks';
|
||||
import { Dialog, useToast } from '@umami/react-zen';
|
||||
import { useToast } from '@umami/react-zen';
|
||||
import { TeamMemberEditForm } from './TeamMemberEditForm';
|
||||
import { ActionButton } from '@/components/input/ActionButton';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
import { Edit } from '@/components/icons';
|
||||
|
||||
export function TeamMemberEditButton({
|
||||
|
|
@ -26,18 +26,21 @@ export function TeamMemberEditButton({
|
|||
};
|
||||
|
||||
return (
|
||||
<ActionButton title={formatMessage(labels.edit)} icon={<Edit />}>
|
||||
<Dialog title={formatMessage(labels.editMember)} style={{ width: 400 }}>
|
||||
{({ close }) => (
|
||||
<TeamMemberEditForm
|
||||
teamId={teamId}
|
||||
userId={userId}
|
||||
role={role}
|
||||
onSave={handleSave}
|
||||
onClose={close}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</ActionButton>
|
||||
<DialogButton
|
||||
icon={<Edit />}
|
||||
title={formatMessage(labels.editMember)}
|
||||
variant="quiet"
|
||||
width="400px"
|
||||
>
|
||||
{({ close }) => (
|
||||
<TeamMemberEditForm
|
||||
teamId={teamId}
|
||||
userId={userId}
|
||||
role={role}
|
||||
onSave={handleSave}
|
||||
onClose={close}
|
||||
/>
|
||||
)}
|
||||
</DialogButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ import { ConfirmationForm } from '@/components/common/ConfirmationForm';
|
|||
import { useDeleteQuery, useMessages, useModified } from '@/components/hooks';
|
||||
import { messages } from '@/components/messages';
|
||||
import { Trash } from '@/components/icons';
|
||||
import { Dialog } from '@umami/react-zen';
|
||||
import { ActionButton } from '@/components/input/ActionButton';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
|
||||
export function TeamMemberRemoveButton({
|
||||
teamId,
|
||||
|
|
@ -32,27 +31,30 @@ export function TeamMemberRemoveButton({
|
|||
};
|
||||
|
||||
return (
|
||||
<ActionButton title={formatMessage(labels.delete)} icon={<Trash />}>
|
||||
<Dialog title={formatMessage(labels.removeMember)} style={{ width: 400 }}>
|
||||
{({ close }) => (
|
||||
<ConfirmationForm
|
||||
message={
|
||||
<FormattedMessage
|
||||
{...messages.confirmRemove}
|
||||
values={{
|
||||
target: <b>{userName}</b>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
isLoading={isPending}
|
||||
error={error}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.remove)}
|
||||
buttonVariant="danger"
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</ActionButton>
|
||||
<DialogButton
|
||||
icon={<Trash />}
|
||||
title={formatMessage(labels.confirm)}
|
||||
variant="quiet"
|
||||
width="400px"
|
||||
>
|
||||
{({ close }) => (
|
||||
<ConfirmationForm
|
||||
message={
|
||||
<FormattedMessage
|
||||
{...messages.confirmRemove}
|
||||
values={{
|
||||
target: <b>{userName}</b>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
isLoading={isPending}
|
||||
error={error}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.remove)}
|
||||
buttonVariant="danger"
|
||||
/>
|
||||
)}
|
||||
</DialogButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { useMessages, useModified } from '@/components/hooks';
|
||||
import { Button, Icon, Modal, Dialog, DialogTrigger, Text, useToast } from '@umami/react-zen';
|
||||
import { useToast } from '@umami/react-zen';
|
||||
import { Plus } from '@/components/icons';
|
||||
import { WebsiteAddForm } from './WebsiteAddForm';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
|
||||
export function WebsiteAddButton({ teamId, onSave }: { teamId: string; onSave?: () => void }) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
|
|
@ -15,18 +16,13 @@ export function WebsiteAddButton({ teamId, onSave }: { teamId: string; onSave?:
|
|||
};
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<Button data-test="button-website-add" variant="primary">
|
||||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.addWebsite)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.addWebsite)} style={{ width: 400 }}>
|
||||
{({ close }) => <WebsiteAddForm teamId={teamId} onSave={handleSave} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</DialogTrigger>
|
||||
<DialogButton
|
||||
icon={<Plus />}
|
||||
label={formatMessage(labels.addWebsite)}
|
||||
variant="primary"
|
||||
width="400px"
|
||||
>
|
||||
{({ close }) => <WebsiteAddForm teamId={teamId} onSave={handleSave} onClose={close} />}
|
||||
</DialogButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export function GoalsPage({ websiteId }: { websiteId: string }) {
|
|||
</SectionHeader>
|
||||
<LoadingPanel data={data} isLoading={isLoading} error={error}>
|
||||
{data && (
|
||||
<Grid columns="1fr 1fr" gap>
|
||||
<Grid columns={{ xs: '1fr', md: '1fr 1fr' }} gap>
|
||||
{data['data'].map((report: any) => (
|
||||
<Panel key={report.id}>
|
||||
<Goal {...report} startDate={startDate} endDate={endDate} />
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { endOfMonth, startOfMonth } from 'date-fns';
|
|||
export function RetentionPage({ websiteId }: { websiteId: string }) {
|
||||
const {
|
||||
dateRange: { startDate },
|
||||
} = useDateRange(websiteId, { ignoreOffset: true });
|
||||
} = useDateRange();
|
||||
|
||||
const monthStartDate = startOfMonth(startDate);
|
||||
const monthEndDate = endOfMonth(startDate);
|
||||
|
|
|
|||
|
|
@ -1,29 +1,16 @@
|
|||
import { Button, DialogTrigger, Modal, Text, Icon, Dialog } from '@umami/react-zen';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Plus } from '@/components/icons';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
import { CohortEditForm } from './CohortEditForm';
|
||||
|
||||
export function CohortAddButton({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<Button variant="primary">
|
||||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.cohort)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog
|
||||
title={formatMessage(labels.cohort)}
|
||||
style={{ width: 800, minHeight: 300, maxHeight: '90vh' }}
|
||||
>
|
||||
{({ close }) => {
|
||||
return <CohortEditForm websiteId={websiteId} onClose={close} />;
|
||||
}}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</DialogTrigger>
|
||||
<DialogButton icon={<Plus />} label={formatMessage(labels.cohort)} variant="primary">
|
||||
{({ close }) => {
|
||||
return <CohortEditForm websiteId={websiteId} onClose={close} />;
|
||||
}}
|
||||
</DialogButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { Dialog } from '@umami/react-zen';
|
||||
import { ActionButton } from '@/components/input/ActionButton';
|
||||
import { Trash } from '@/components/icons';
|
||||
import { ConfirmationForm } from '@/components/common/ConfirmationForm';
|
||||
import { messages } from '@/components/messages';
|
||||
import { useDeleteQuery, useMessages } from '@/components/hooks';
|
||||
import { ConfirmationForm } from '@/components/common/ConfirmationForm';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
|
||||
export function CohortDeleteButton({
|
||||
cohortId,
|
||||
|
|
@ -32,27 +31,30 @@ export function CohortDeleteButton({
|
|||
};
|
||||
|
||||
return (
|
||||
<ActionButton title={formatMessage(labels.delete)} icon={<Trash />}>
|
||||
<Dialog title={formatMessage(labels.confirm)} style={{ width: 400 }}>
|
||||
{({ close }) => (
|
||||
<ConfirmationForm
|
||||
message={
|
||||
<FormattedMessage
|
||||
{...messages.confirmRemove}
|
||||
values={{
|
||||
target: <b>{name}</b>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
isLoading={isPending}
|
||||
error={error}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.delete)}
|
||||
buttonVariant="danger"
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</ActionButton>
|
||||
<DialogButton
|
||||
icon={<Trash />}
|
||||
variant="quiet"
|
||||
title={formatMessage(labels.confirm)}
|
||||
width="400px"
|
||||
>
|
||||
{({ close }) => (
|
||||
<ConfirmationForm
|
||||
message={
|
||||
<FormattedMessage
|
||||
{...messages.confirmRemove}
|
||||
values={{
|
||||
target: <b>{name}</b>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
isLoading={isPending}
|
||||
error={error}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.delete)}
|
||||
buttonVariant="danger"
|
||||
/>
|
||||
)}
|
||||
</DialogButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { ActionButton } from '@/components/input/ActionButton';
|
||||
import { Edit } from '@/components/icons';
|
||||
import { Dialog } from '@umami/react-zen';
|
||||
import { CohortEditForm } from '@/app/(main)/websites/[websiteId]/cohorts/CohortEditForm';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Filter } from '@/lib/types';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
|
||||
export function CohortEditButton({
|
||||
cohortId,
|
||||
|
|
@ -17,22 +16,23 @@ export function CohortEditButton({
|
|||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<ActionButton title={formatMessage(labels.edit)} icon={<Edit />}>
|
||||
<Dialog
|
||||
title={formatMessage(labels.cohort)}
|
||||
style={{ width: 800, minHeight: 300, maxHeight: '90vh' }}
|
||||
>
|
||||
{({ close }) => {
|
||||
return (
|
||||
<CohortEditForm
|
||||
cohortId={cohortId}
|
||||
websiteId={websiteId}
|
||||
filters={filters}
|
||||
onClose={close}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Dialog>
|
||||
</ActionButton>
|
||||
<DialogButton
|
||||
icon={<Edit />}
|
||||
variant="quiet"
|
||||
title={formatMessage(labels.cohort)}
|
||||
width="800px"
|
||||
minHeight="300px"
|
||||
>
|
||||
{({ close }) => {
|
||||
return (
|
||||
<CohortEditForm
|
||||
cohortId={cohortId}
|
||||
websiteId={websiteId}
|
||||
filters={filters}
|
||||
onClose={close}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</DialogButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export function CohortEditForm({
|
|||
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.action)}</Label>
|
||||
<Grid columns="260px 1fr" gap>
|
||||
<Grid columns={{ xs: '1fr', md: '1fr 1fr' }} gap>
|
||||
<Column>
|
||||
<FormField
|
||||
name="parameters.action.type"
|
||||
|
|
|
|||
|
|
@ -1,22 +1,17 @@
|
|||
import { DataTable, DataColumn, Row } from '@umami/react-zen';
|
||||
import { DataTable, DataColumn, Row, DataTableProps } from '@umami/react-zen';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { DateDistance } from '@/components/common/DateDistance';
|
||||
import { filtersObjectToArray } from '@/lib/params';
|
||||
import { CohortEditButton } from '@/app/(main)/websites/[websiteId]/cohorts/CohortEditButton';
|
||||
import { CohortDeleteButton } from '@/app/(main)/websites/[websiteId]/cohorts/CohortDeleteButton';
|
||||
import Link from 'next/link';
|
||||
|
||||
export function CohortsTable({ data = [] }) {
|
||||
export function CohortsTable(props: DataTableProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { websiteId, renderUrl } = useNavigation();
|
||||
|
||||
if (data.length === 0) {
|
||||
return <Empty />;
|
||||
}
|
||||
|
||||
return (
|
||||
<DataTable data={data}>
|
||||
<DataTable {...props}>
|
||||
<DataColumn id="name" label={formatMessage(labels.name)}>
|
||||
{(row: any) => (
|
||||
<Link href={renderUrl(`/websites/${websiteId}?cohort=${row.id}`, false)}>{row.name}</Link>
|
||||
|
|
|
|||
|
|
@ -1,29 +1,16 @@
|
|||
import { Button, DialogTrigger, Modal, Text, Icon, Dialog } from '@umami/react-zen';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Plus } from '@/components/icons';
|
||||
import { SegmentEditForm } from './SegmentEditForm';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
|
||||
export function SegmentAddButton({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<Button variant="primary">
|
||||
<Icon>
|
||||
<Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.segment)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog
|
||||
title={formatMessage(labels.segment)}
|
||||
style={{ width: 800, minHeight: 300, maxHeight: '90vh' }}
|
||||
>
|
||||
{({ close }) => {
|
||||
return <SegmentEditForm websiteId={websiteId} onClose={close} />;
|
||||
}}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</DialogTrigger>
|
||||
<DialogButton icon={<Plus />} label={formatMessage(labels.segment)} variant="primary">
|
||||
{({ close }) => {
|
||||
return <SegmentEditForm websiteId={websiteId} onClose={close} />;
|
||||
}}
|
||||
</DialogButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { Dialog } from '@umami/react-zen';
|
||||
import { ActionButton } from '@/components/input/ActionButton';
|
||||
import { Trash } from '@/components/icons';
|
||||
import { ConfirmationForm } from '@/components/common/ConfirmationForm';
|
||||
import { messages } from '@/components/messages';
|
||||
import { useDeleteQuery, useMessages } from '@/components/hooks';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
|
||||
export function SegmentDeleteButton({
|
||||
segmentId,
|
||||
|
|
@ -32,27 +31,30 @@ export function SegmentDeleteButton({
|
|||
};
|
||||
|
||||
return (
|
||||
<ActionButton title={formatMessage(labels.delete)} icon={<Trash />}>
|
||||
<Dialog title={formatMessage(labels.confirm)} style={{ width: 400 }}>
|
||||
{({ close }) => (
|
||||
<ConfirmationForm
|
||||
message={
|
||||
<FormattedMessage
|
||||
{...messages.confirmRemove}
|
||||
values={{
|
||||
target: <b>{name}</b>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
isLoading={isPending}
|
||||
error={error}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.delete)}
|
||||
buttonVariant="danger"
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</ActionButton>
|
||||
<DialogButton
|
||||
icon={<Trash />}
|
||||
title={formatMessage(labels.confirm)}
|
||||
variant="quiet"
|
||||
width="600px"
|
||||
>
|
||||
{({ close }) => (
|
||||
<ConfirmationForm
|
||||
message={
|
||||
<FormattedMessage
|
||||
{...messages.confirmRemove}
|
||||
values={{
|
||||
target: <b>{name}</b>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
isLoading={isPending}
|
||||
error={error}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.delete)}
|
||||
buttonVariant="danger"
|
||||
/>
|
||||
)}
|
||||
</DialogButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { Dialog } from '@umami/react-zen';
|
||||
import { ActionButton } from '@/components/input/ActionButton';
|
||||
import { Edit } from '@/components/icons';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { SegmentEditForm } from './SegmentEditForm';
|
||||
import { Filter } from '@/lib/types';
|
||||
import { DialogButton } from '@/components/input/DialogButton';
|
||||
|
||||
export function SegmentEditButton({
|
||||
segmentId,
|
||||
|
|
@ -17,22 +16,22 @@ export function SegmentEditButton({
|
|||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<ActionButton title={formatMessage(labels.edit)} icon={<Edit />}>
|
||||
<Dialog
|
||||
title={formatMessage(labels.segment)}
|
||||
style={{ width: 800, minHeight: 300, maxHeight: '90vh' }}
|
||||
>
|
||||
{({ close }) => {
|
||||
return (
|
||||
<SegmentEditForm
|
||||
segmentId={segmentId}
|
||||
websiteId={websiteId}
|
||||
filters={filters}
|
||||
onClose={close}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Dialog>
|
||||
</ActionButton>
|
||||
<DialogButton
|
||||
icon={<Edit />}
|
||||
title={formatMessage(labels.segment)}
|
||||
variant="quiet"
|
||||
width="800px"
|
||||
>
|
||||
{({ close }) => {
|
||||
return (
|
||||
<SegmentEditForm
|
||||
segmentId={segmentId}
|
||||
websiteId={websiteId}
|
||||
filters={filters}
|
||||
onClose={close}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</DialogButton>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,16 @@
|
|||
import { DataTable, DataColumn, Row } from '@umami/react-zen';
|
||||
import { DataTable, DataColumn, Row, DataTableProps } from '@umami/react-zen';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { DateDistance } from '@/components/common/DateDistance';
|
||||
import { SegmentEditButton } from '@/app/(main)/websites/[websiteId]/segments/SegmentEditButton';
|
||||
import { SegmentDeleteButton } from '@/app/(main)/websites/[websiteId]/segments/SegmentDeleteButton';
|
||||
import Link from 'next/link';
|
||||
|
||||
export function SegmentsTable({ data = [] }) {
|
||||
export function SegmentsTable(props: DataTableProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { websiteId, renderUrl } = useNavigation();
|
||||
|
||||
if (data.length === 0) {
|
||||
return <Empty />;
|
||||
}
|
||||
|
||||
return (
|
||||
<DataTable data={data}>
|
||||
<DataTable {...props}>
|
||||
<DataColumn id="name" label={formatMessage(labels.name)}>
|
||||
{(row: any) => (
|
||||
<Link href={renderUrl(`/websites/${websiteId}?segment=${row.id}`, false)}>
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export function DataGrid({
|
|||
return (
|
||||
<Column gap="4" minHeight="300px">
|
||||
{allowSearch && (
|
||||
<Row alignItems="center" justifyContent="space-between">
|
||||
<Row alignItems="center" justifyContent="space-between" wrap="wrap" gap>
|
||||
<SearchField
|
||||
value={search}
|
||||
onSearch={handleSearch}
|
||||
|
|
|
|||
|
|
@ -104,8 +104,8 @@ export function FilterRecord({
|
|||
</Select>
|
||||
)}
|
||||
</Grid>
|
||||
<Column justifyContent="flex-end">
|
||||
<Button variant="quiet" onPress={() => onRemove?.(name)}>
|
||||
<Column justifyContent="flex-start">
|
||||
<Button onPress={() => onRemove?.(name)}>
|
||||
<Icon>
|
||||
<X />
|
||||
</Icon>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export function MobileMenu(props: DialogProps) {
|
|||
<Menu />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Modal position="left" offset="80px">
|
||||
<Modal placement="left" offset="80px">
|
||||
<Dialog variant="sheet" {...props} />
|
||||
</Modal>
|
||||
</DialogTrigger>
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ export * from './useGlobalState';
|
|||
export * from './useLanguageNames';
|
||||
export * from './useLocale';
|
||||
export * from './useMessages';
|
||||
export * from './useMobile';
|
||||
export * from './useModified';
|
||||
export * from './useNavigation';
|
||||
export * from './usePagedQuery';
|
||||
|
|
|
|||
8
src/components/hooks/useMobile.ts
Normal file
8
src/components/hooks/useMobile.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { useBreakpoint } from '@umami/react-zen';
|
||||
|
||||
export function useMobile() {
|
||||
const breakpoint = useBreakpoint();
|
||||
const isMobile = ['xs', 'sm', 'md'].includes(breakpoint);
|
||||
|
||||
return { breakpoint, isMobile };
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Button, Icon, Modal, Text, DialogTrigger } from '@umami/react-zen';
|
||||
|
||||
export function ActionButton({
|
||||
onClick,
|
||||
icon,
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
onClick?: () => void;
|
||||
icon?: ReactNode;
|
||||
title?: string;
|
||||
children?: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<Text title={title}>
|
||||
<Button variant="quiet" onPress={onClick}>
|
||||
<Icon>{icon}</Icon>
|
||||
</Button>
|
||||
</Text>
|
||||
<Modal>{children}</Modal>
|
||||
</DialogTrigger>
|
||||
);
|
||||
}
|
||||
57
src/components/input/DialogButton.tsx
Normal file
57
src/components/input/DialogButton.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { CSSProperties, ReactNode } from 'react';
|
||||
import {
|
||||
Button,
|
||||
ButtonProps,
|
||||
Modal,
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogProps,
|
||||
IconLabel,
|
||||
} from '@umami/react-zen';
|
||||
import { useMobile } from '@/components/hooks';
|
||||
|
||||
export interface DialogButtonProps extends Omit<ButtonProps, 'children'> {
|
||||
icon?: ReactNode;
|
||||
label?: ReactNode;
|
||||
title?: ReactNode;
|
||||
width?: string;
|
||||
height?: string;
|
||||
minWidth?: string;
|
||||
minHeight?: string;
|
||||
children?: DialogProps['children'];
|
||||
}
|
||||
|
||||
export function DialogButton({
|
||||
icon,
|
||||
label,
|
||||
title,
|
||||
width = '800px',
|
||||
height,
|
||||
minWidth,
|
||||
minHeight,
|
||||
children,
|
||||
...props
|
||||
}: DialogButtonProps) {
|
||||
const { isMobile } = useMobile();
|
||||
const style: CSSProperties = { width, height, minWidth, minHeight, padding: '32px' };
|
||||
|
||||
if (isMobile) {
|
||||
style.width = '100%';
|
||||
style.height = '100%';
|
||||
style.overflowY = 'auto';
|
||||
}
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<Button {...props}>
|
||||
<IconLabel icon={icon} label={label} />
|
||||
</Button>
|
||||
|
||||
<Modal placement={isMobile ? 'fullscreen' : 'center'}>
|
||||
<Dialog variant={isMobile ? 'sheet' : undefined} title={title || label} style={style}>
|
||||
{children}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</DialogTrigger>
|
||||
);
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ import {
|
|||
MenuItem,
|
||||
Icon,
|
||||
} from '@umami/react-zen';
|
||||
import { useFields, useMessages } from '@/components/hooks';
|
||||
import { useFields, useMessages, useMobile } from '@/components/hooks';
|
||||
import { Plus } from '@/components/icons';
|
||||
import { FilterRecord } from '@/components/common/FilterRecord';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
|
|
@ -30,6 +30,7 @@ export function FieldFilters({ websiteId, value, exclude = [], onChange }: Field
|
|||
const { fields } = useFields();
|
||||
const startDate = subMonths(endOfDay(new Date()), 6);
|
||||
const endDate = endOfDay(new Date());
|
||||
const { isMobile } = useMobile();
|
||||
|
||||
const updateFilter = (name: string, props: Record<string, any>) => {
|
||||
onChange(value.map(filter => (filter.name === name ? { ...filter, ...props } : filter)));
|
||||
|
|
@ -60,8 +61,11 @@ export function FieldFilters({ websiteId, value, exclude = [], onChange }: Field
|
|||
<Plus />
|
||||
</Icon>
|
||||
</Button>
|
||||
<Popover placement="bottom start">
|
||||
<Menu onAction={handleAdd}>
|
||||
<Popover placement={isMobile ? 'left' : 'bottom start'} shouldFlip>
|
||||
<Menu
|
||||
onAction={handleAdd}
|
||||
style={{ maxHeight: 'calc(100vh - 2rem)', overflowY: 'auto' }}
|
||||
>
|
||||
{fields
|
||||
.filter(({ name }) => !exclude.includes(name))
|
||||
.map(field => {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export function WebsiteFilterButton({
|
|||
</Icon>
|
||||
{showText && <Text>{formatMessage(labels.filter)}</Text>}
|
||||
</Button>
|
||||
<Modal position={isMobile ? 'fullscreen' : 'center'}>
|
||||
<Modal placement={isMobile ? 'fullscreen' : 'center'}>
|
||||
<Dialog title={formatMessage(labels.filters)}>
|
||||
{({ close }) => {
|
||||
return <FilterEditForm websiteId={websiteId} onChange={handleChange} onClose={close} />;
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@ export * from '@/components/common/SectionHeader';
|
|||
export * from '@/components/common/SideMenu';
|
||||
export * from '@/components/common/TypeConfirmationForm';
|
||||
|
||||
export * from '@/components/input/ActionButton';
|
||||
export * from '@/components/input/DateFilter';
|
||||
export * from '@/components/input/DialogButton';
|
||||
export * from '@/components/input/DownloadButton';
|
||||
export * from '@/components/input/ExportButton';
|
||||
export * from '@/components/input/FilterButtons';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue