mirror of
https://github.com/umami-software/umami.git
synced 2026-02-16 10:35:35 +01:00
Add website binding to boards with filter and date controls.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d3d86f43fa
commit
f66a508892
5 changed files with 48 additions and 5 deletions
|
|
@ -1,5 +1,7 @@
|
||||||
import { Button, Form, FormField, FormSubmitButton, Row, TextField } from '@umami/react-zen';
|
import { Button, Form, FormField, FormSubmitButton, Row, Text, TextField } from '@umami/react-zen';
|
||||||
|
import { useState } from 'react';
|
||||||
import { useMessages, useUpdateQuery } from '@/components/hooks';
|
import { useMessages, useUpdateQuery } from '@/components/hooks';
|
||||||
|
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||||
|
|
||||||
export function BoardAddForm({
|
export function BoardAddForm({
|
||||||
teamId,
|
teamId,
|
||||||
|
|
@ -12,10 +14,11 @@ export function BoardAddForm({
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { mutateAsync, error, isPending } = useUpdateQuery('/boards', { teamId });
|
const { mutateAsync, error, isPending } = useUpdateQuery('/boards', { teamId });
|
||||||
|
const [websiteId, setWebsiteId] = useState<string>();
|
||||||
|
|
||||||
const handleSubmit = async (data: any) => {
|
const handleSubmit = async (data: any) => {
|
||||||
await mutateAsync(
|
await mutateAsync(
|
||||||
{ type: 'board', ...data },
|
{ type: 'board', ...data, parameters: { websiteId } },
|
||||||
{
|
{
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
onSave?.();
|
onSave?.();
|
||||||
|
|
@ -43,6 +46,10 @@ export function BoardAddForm({
|
||||||
>
|
>
|
||||||
<TextField asTextArea autoComplete="off" />
|
<TextField asTextArea autoComplete="off" />
|
||||||
</FormField>
|
</FormField>
|
||||||
|
<Row alignItems="center" gap="3" paddingTop="3">
|
||||||
|
<Text>{formatMessage(labels.website)}</Text>
|
||||||
|
<WebsiteSelect websiteId={websiteId} teamId={teamId} onChange={setWebsiteId} />
|
||||||
|
</Row>
|
||||||
<Row justifyContent="flex-end" paddingTop="3" gap="3">
|
<Row justifyContent="flex-end" paddingTop="3" gap="3">
|
||||||
{onClose && (
|
{onClose && (
|
||||||
<Button isDisabled={isPending} onPress={onClose}>
|
<Button isDisabled={isPending} onPress={onClose}>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
import { Button, Column, Grid, Heading, LoadingButton, Row, TextField } from '@umami/react-zen';
|
import {
|
||||||
|
Button,
|
||||||
|
Column,
|
||||||
|
Grid,
|
||||||
|
Heading,
|
||||||
|
LoadingButton,
|
||||||
|
Row,
|
||||||
|
Text,
|
||||||
|
TextField,
|
||||||
|
} from '@umami/react-zen';
|
||||||
import { IconLabel } from '@/components/common/IconLabel';
|
import { IconLabel } from '@/components/common/IconLabel';
|
||||||
import { LinkButton } from '@/components/common/LinkButton';
|
import { LinkButton } from '@/components/common/LinkButton';
|
||||||
import { PageHeader } from '@/components/common/PageHeader';
|
import { PageHeader } from '@/components/common/PageHeader';
|
||||||
import { useBoard, useMessages, useNavigation } from '@/components/hooks';
|
import { useBoard, useMessages, useNavigation, useWebsiteQuery } from '@/components/hooks';
|
||||||
import { Edit } from '@/components/icons';
|
import { Edit } from '@/components/icons';
|
||||||
|
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||||
|
|
||||||
export function BoardHeader() {
|
export function BoardHeader() {
|
||||||
const { board, editing } = useBoard();
|
const { board, editing } = useBoard();
|
||||||
|
|
@ -19,9 +29,11 @@ function BoardViewHeader() {
|
||||||
const { board } = useBoard();
|
const { board } = useBoard();
|
||||||
const { renderUrl } = useNavigation();
|
const { renderUrl } = useNavigation();
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { data: website } = useWebsiteQuery(board?.parameters?.websiteId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageHeader title={board?.name} description={board?.description}>
|
<PageHeader title={board?.name} description={board?.description}>
|
||||||
|
{website?.name && <Text>{website.name}</Text>}
|
||||||
<LinkButton href={renderUrl(`/boards/${board?.id}/edit`, false)}>
|
<LinkButton href={renderUrl(`/boards/${board?.id}/edit`, false)}>
|
||||||
<IconLabel icon={<Edit />}>{formatMessage(labels.edit)}</IconLabel>
|
<IconLabel icon={<Edit />}>{formatMessage(labels.edit)}</IconLabel>
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
|
|
@ -43,6 +55,10 @@ function BoardEditHeader() {
|
||||||
updateBoard({ description: value });
|
updateBoard({ description: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleWebsiteChange = (websiteId: string) => {
|
||||||
|
updateBoard({ parameters: { ...board.parameters, websiteId } });
|
||||||
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
await saveBoard();
|
await saveBoard();
|
||||||
if (board.id) {
|
if (board.id) {
|
||||||
|
|
@ -93,6 +109,10 @@ function BoardEditHeader() {
|
||||||
{board?.description}
|
{board?.description}
|
||||||
</TextField>
|
</TextField>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row alignItems="center" gap="3">
|
||||||
|
<Text>{formatMessage(labels.website)}</Text>
|
||||||
|
<WebsiteSelect websiteId={board?.parameters?.websiteId} onChange={handleWebsiteChange} />
|
||||||
|
</Row>
|
||||||
</Column>
|
</Column>
|
||||||
<Column justifyContent="center" alignItems="flex-end">
|
<Column justifyContent="center" alignItems="flex-end">
|
||||||
<Row gap="3">
|
<Row gap="3">
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ import { Column } from '@umami/react-zen';
|
||||||
import { BoardBody } from '@/app/(main)/boards/[boardId]/BoardBody';
|
import { BoardBody } from '@/app/(main)/boards/[boardId]/BoardBody';
|
||||||
import { BoardHeader } from '@/app/(main)/boards/[boardId]/BoardHeader';
|
import { BoardHeader } from '@/app/(main)/boards/[boardId]/BoardHeader';
|
||||||
import { BoardProvider } from '@/app/(main)/boards/BoardProvider';
|
import { BoardProvider } from '@/app/(main)/boards/BoardProvider';
|
||||||
|
import { WebsiteControls } from '@/app/(main)/websites/[websiteId]/WebsiteControls';
|
||||||
import { PageBody } from '@/components/common/PageBody';
|
import { PageBody } from '@/components/common/PageBody';
|
||||||
|
import { useBoard } from '@/components/hooks';
|
||||||
|
|
||||||
export function BoardPage({ boardId, editing = false }: { boardId?: string; editing?: boolean }) {
|
export function BoardPage({ boardId, editing = false }: { boardId?: string; editing?: boolean }) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -11,9 +13,21 @@ export function BoardPage({ boardId, editing = false }: { boardId?: string; edit
|
||||||
<PageBody>
|
<PageBody>
|
||||||
<Column>
|
<Column>
|
||||||
<BoardHeader />
|
<BoardHeader />
|
||||||
|
<BoardControls />
|
||||||
<BoardBody />
|
<BoardBody />
|
||||||
</Column>
|
</Column>
|
||||||
</PageBody>
|
</PageBody>
|
||||||
</BoardProvider>
|
</BoardProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function BoardControls() {
|
||||||
|
const { board, editing } = useBoard();
|
||||||
|
const websiteId = board?.parameters?.websiteId;
|
||||||
|
|
||||||
|
if (editing || !websiteId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <WebsiteControls websiteId={websiteId} allowCompare={true} />;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ export async function POST(request: Request) {
|
||||||
slug: z.string().max(100),
|
slug: z.string().max(100),
|
||||||
userId: z.uuid().nullable().optional(),
|
userId: z.uuid().nullable().optional(),
|
||||||
teamId: z.uuid().nullable().optional(),
|
teamId: z.uuid().nullable().optional(),
|
||||||
|
parameters: z.object({ websiteId: z.uuid().optional() }).passthrough().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { auth, body, error } = await parseRequest(request, schema);
|
const { auth, body, error } = await parseRequest(request, schema);
|
||||||
|
|
@ -50,7 +51,7 @@ export async function POST(request: Request) {
|
||||||
const data = {
|
const data = {
|
||||||
...body,
|
...body,
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
parameters: {},
|
parameters: body.parameters ?? {},
|
||||||
slug: uuid(),
|
slug: uuid(),
|
||||||
userId: !teamId ? auth.user.id : undefined,
|
userId: !teamId ? auth.user.id : undefined,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,7 @@ export interface BoardRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BoardParameters {
|
export interface BoardParameters {
|
||||||
|
websiteId?: string;
|
||||||
rows?: BoardRow[];
|
rows?: BoardRow[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue