mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 04:37:11 +01:00
Board editing.
This commit is contained in:
parent
68c56060b3
commit
d9f698ca42
8 changed files with 183 additions and 86 deletions
|
|
@ -90,3 +90,7 @@ DEBUG # Debug namespaces (e.g., umami:*)
|
||||||
- Node.js 18.18+
|
- Node.js 18.18+
|
||||||
- PostgreSQL 12.14+
|
- PostgreSQL 12.14+
|
||||||
- pnpm (package manager)
|
- pnpm (package manager)
|
||||||
|
|
||||||
|
## Git Workflow
|
||||||
|
|
||||||
|
Always ask for confirmation before running `git commit` or `git push`.
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@
|
||||||
"stylelint-config-css-modules": "^4.5.1",
|
"stylelint-config-css-modules": "^4.5.1",
|
||||||
"stylelint-config-prettier": "^9.0.3",
|
"stylelint-config-prettier": "^9.0.3",
|
||||||
"stylelint-config-recommended": "^14.0.0",
|
"stylelint-config-recommended": "^14.0.0",
|
||||||
"tar": "^6.1.2",
|
"tar": "^7.5.3",
|
||||||
"ts-jest": "^29.4.6",
|
"ts-jest": "^29.4.6",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tsup": "^8.5.0",
|
"tsup": "^8.5.0",
|
||||||
|
|
|
||||||
78
pnpm-lock.yaml
generated
78
pnpm-lock.yaml
generated
|
|
@ -316,8 +316,8 @@ importers:
|
||||||
specifier: ^14.0.0
|
specifier: ^14.0.0
|
||||||
version: 14.0.1(stylelint@15.11.0(typescript@5.9.3))
|
version: 14.0.1(stylelint@15.11.0(typescript@5.9.3))
|
||||||
tar:
|
tar:
|
||||||
specifier: ^6.1.2
|
specifier: ^7.5.3
|
||||||
version: 6.2.1
|
version: 7.5.3
|
||||||
ts-jest:
|
ts-jest:
|
||||||
specifier: ^29.4.6
|
specifier: ^29.4.6
|
||||||
version: 29.4.6(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.12)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.10.8)(ts-node@10.9.2(@types/node@24.10.8)(typescript@5.9.3)))(typescript@5.9.3)
|
version: 29.4.6(@babel/core@7.28.3)(@jest/transform@29.7.0)(@jest/types@30.0.5)(babel-jest@29.7.0(@babel/core@7.28.3))(esbuild@0.25.12)(jest-util@30.0.5)(jest@29.7.0(@types/node@24.10.8)(ts-node@10.9.2(@types/node@24.10.8)(typescript@5.9.3)))(typescript@5.9.3)
|
||||||
|
|
@ -1479,6 +1479,10 @@ packages:
|
||||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
'@isaacs/fs-minipass@4.0.1':
|
||||||
|
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
'@istanbuljs/load-nyc-config@1.1.0':
|
'@istanbuljs/load-nyc-config@1.1.0':
|
||||||
resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
|
resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
@ -3279,9 +3283,9 @@ packages:
|
||||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||||
engines: {node: '>= 14.16.0'}
|
engines: {node: '>= 14.16.0'}
|
||||||
|
|
||||||
chownr@2.0.0:
|
chownr@3.0.0:
|
||||||
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
|
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
ci-info@3.9.0:
|
ci-info@3.9.0:
|
||||||
resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
|
resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
|
||||||
|
|
@ -4066,10 +4070,6 @@ packages:
|
||||||
resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
|
resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
fs-minipass@2.1.0:
|
|
||||||
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
|
|
||||||
engines: {node: '>= 8'}
|
|
||||||
|
|
||||||
fs.realpath@1.0.0:
|
fs.realpath@1.0.0:
|
||||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||||
|
|
||||||
|
|
@ -5180,21 +5180,13 @@ packages:
|
||||||
minimist@1.2.8:
|
minimist@1.2.8:
|
||||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||||
|
|
||||||
minipass@3.3.6:
|
|
||||||
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
minipass@5.0.0:
|
|
||||||
resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
minipass@7.1.2:
|
minipass@7.1.2:
|
||||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
||||||
minizlib@2.1.2:
|
minizlib@3.1.0:
|
||||||
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
|
resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 18'}
|
||||||
|
|
||||||
mkdirp@1.0.4:
|
mkdirp@1.0.4:
|
||||||
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
|
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
|
||||||
|
|
@ -6781,9 +6773,9 @@ packages:
|
||||||
resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==}
|
resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
tar@6.2.1:
|
tar@7.5.3:
|
||||||
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
|
resolution: {integrity: sha512-ENg5JUHUm2rDD7IvKNFGzyElLXNjachNLp6RaGf4+JOgxXHkqA+gq81ZAMCUmtMtqBsoU62lcp6S27g1LCYGGQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
terser@5.43.1:
|
terser@5.43.1:
|
||||||
resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==}
|
resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==}
|
||||||
|
|
@ -7190,6 +7182,10 @@ packages:
|
||||||
yallist@4.0.0:
|
yallist@4.0.0:
|
||||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||||
|
|
||||||
|
yallist@5.0.0:
|
||||||
|
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
yaml@1.10.2:
|
yaml@1.10.2:
|
||||||
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
|
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
@ -8224,6 +8220,10 @@ snapshots:
|
||||||
wrap-ansi: 8.1.0
|
wrap-ansi: 8.1.0
|
||||||
wrap-ansi-cjs: wrap-ansi@7.0.0
|
wrap-ansi-cjs: wrap-ansi@7.0.0
|
||||||
|
|
||||||
|
'@isaacs/fs-minipass@4.0.1':
|
||||||
|
dependencies:
|
||||||
|
minipass: 7.1.2
|
||||||
|
|
||||||
'@istanbuljs/load-nyc-config@1.1.0':
|
'@istanbuljs/load-nyc-config@1.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
camelcase: 5.3.1
|
camelcase: 5.3.1
|
||||||
|
|
@ -10609,7 +10609,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
readdirp: 4.1.2
|
readdirp: 4.1.2
|
||||||
|
|
||||||
chownr@2.0.0: {}
|
chownr@3.0.0: {}
|
||||||
|
|
||||||
ci-info@3.9.0: {}
|
ci-info@3.9.0: {}
|
||||||
|
|
||||||
|
|
@ -11576,10 +11576,6 @@ snapshots:
|
||||||
jsonfile: 6.2.0
|
jsonfile: 6.2.0
|
||||||
universalify: 2.0.1
|
universalify: 2.0.1
|
||||||
|
|
||||||
fs-minipass@2.1.0:
|
|
||||||
dependencies:
|
|
||||||
minipass: 3.3.6
|
|
||||||
|
|
||||||
fs.realpath@1.0.0: {}
|
fs.realpath@1.0.0: {}
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
|
|
@ -12882,18 +12878,11 @@ snapshots:
|
||||||
|
|
||||||
minimist@1.2.8: {}
|
minimist@1.2.8: {}
|
||||||
|
|
||||||
minipass@3.3.6:
|
|
||||||
dependencies:
|
|
||||||
yallist: 4.0.0
|
|
||||||
|
|
||||||
minipass@5.0.0: {}
|
|
||||||
|
|
||||||
minipass@7.1.2: {}
|
minipass@7.1.2: {}
|
||||||
|
|
||||||
minizlib@2.1.2:
|
minizlib@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
minipass: 3.3.6
|
minipass: 7.1.2
|
||||||
yallist: 4.0.0
|
|
||||||
|
|
||||||
mkdirp@1.0.4: {}
|
mkdirp@1.0.4: {}
|
||||||
|
|
||||||
|
|
@ -14706,14 +14695,13 @@ snapshots:
|
||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
strip-ansi: 6.0.1
|
strip-ansi: 6.0.1
|
||||||
|
|
||||||
tar@6.2.1:
|
tar@7.5.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
chownr: 2.0.0
|
'@isaacs/fs-minipass': 4.0.1
|
||||||
fs-minipass: 2.1.0
|
chownr: 3.0.0
|
||||||
minipass: 5.0.0
|
minipass: 7.1.2
|
||||||
minizlib: 2.1.2
|
minizlib: 3.1.0
|
||||||
mkdirp: 1.0.4
|
yallist: 5.0.0
|
||||||
yallist: 4.0.0
|
|
||||||
|
|
||||||
terser@5.43.1:
|
terser@5.43.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -15132,6 +15120,8 @@ snapshots:
|
||||||
|
|
||||||
yallist@4.0.0: {}
|
yallist@4.0.0: {}
|
||||||
|
|
||||||
|
yallist@5.0.0: {}
|
||||||
|
|
||||||
yaml@1.10.2: {}
|
yaml@1.10.2: {}
|
||||||
|
|
||||||
yaml@2.8.1: {}
|
yaml@2.8.1: {}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ async function checkConnection() {
|
||||||
|
|
||||||
success('Database connection successful.');
|
success('Database connection successful.');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error('Unable to connect to the database: ' + e.message);
|
throw new Error(`Unable to connect to the database: ${e.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { Loading, useToast } from '@umami/react-zen';
|
||||||
import { createContext, type ReactNode, useCallback, useEffect, useState } from 'react';
|
import { createContext, type ReactNode, useCallback, useEffect, useState } from 'react';
|
||||||
import { useApi, useMessages, useModified, useNavigation } from '@/components/hooks';
|
import { useApi, useMessages, useModified, useNavigation } from '@/components/hooks';
|
||||||
import { useBoardQuery } from '@/components/hooks/queries/useBoardQuery';
|
import { useBoardQuery } from '@/components/hooks/queries/useBoardQuery';
|
||||||
import type { Board } from '@/generated/prisma/client';
|
import type { Board } from '@/lib/types';
|
||||||
|
|
||||||
export interface BoardContextValue {
|
export interface BoardContextValue {
|
||||||
board: Partial<Board>;
|
board: Partial<Board>;
|
||||||
|
|
@ -17,6 +17,7 @@ export const BoardContext = createContext<BoardContextValue>(null);
|
||||||
const defaultBoard: Partial<Board> = {
|
const defaultBoard: Partial<Board> = {
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
|
parameters: { rows: [] },
|
||||||
};
|
};
|
||||||
|
|
||||||
export function BoardProvider({ boardId, children }: { boardId?: string; children: ReactNode }) {
|
export function BoardProvider({ boardId, children }: { boardId?: string; children: ReactNode }) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,18 @@
|
||||||
import { Button, Column, Icon, Row } from '@umami/react-zen';
|
import { Box, Button, Column, Icon, Row, Tooltip, TooltipTrigger } from '@umami/react-zen';
|
||||||
import { produce } from 'immer';
|
import { produce } from 'immer';
|
||||||
|
import { Fragment, type ReactElement } from 'react';
|
||||||
|
import { Group, Panel, Separator } from 'react-resizable-panels';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { useBoard } from '@/components/hooks';
|
import { useBoard } from '@/components/hooks';
|
||||||
import { Plus } from '@/components/icons';
|
import { Minus, Plus } from '@/components/icons';
|
||||||
|
import type { BoardColumn as BoardColumnType } from '@/lib/types';
|
||||||
|
|
||||||
|
const CATALOG = {
|
||||||
|
text: {
|
||||||
|
label: 'Text',
|
||||||
|
component: BoardColumn,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export function BoardBody() {
|
export function BoardBody() {
|
||||||
const { board, updateBoard, saveBoard, isPending } = useBoard();
|
const { board, updateBoard, saveBoard, isPending } = useBoard();
|
||||||
|
|
@ -15,55 +25,126 @@ export function BoardBody() {
|
||||||
if (!draft.rows) {
|
if (!draft.rows) {
|
||||||
draft.rows = [];
|
draft.rows = [];
|
||||||
}
|
}
|
||||||
draft.rows.push({ id: uuid(), components: [] });
|
draft.rows.push({ id: uuid(), columns: [{ id: uuid(), component: null }] });
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveRow = (id: string) => {
|
||||||
|
console.log('Removing row', id);
|
||||||
|
updateBoard({
|
||||||
|
parameters: produce(board.parameters, draft => {
|
||||||
|
if (!draft.rows) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
draft.rows = draft.rows.filter(row => row?.id !== id);
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const rows = board?.parameters?.rows ?? [];
|
||||||
|
const minHeight = 300 * (rows.length || 1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Group orientation="vertical" style={{ minHeight }}>
|
||||||
|
{rows.map((row, index) => (
|
||||||
|
<Fragment key={row.id}>
|
||||||
|
<Panel minSize={200}>
|
||||||
|
<BoardRow {...row} rowId={row.id} onRemove={handleRemoveRow} />
|
||||||
|
</Panel>
|
||||||
|
{index < rows.length - 1 && <Separator />}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
<Row>
|
||||||
|
<TooltipTrigger delay={0}>
|
||||||
|
<Button variant="outline" onPress={handleAddRow}>
|
||||||
|
<Icon>
|
||||||
|
<Plus />
|
||||||
|
</Icon>
|
||||||
|
</Button>
|
||||||
|
<Tooltip placement="bottom">Add row</Tooltip>
|
||||||
|
</TooltipTrigger>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BoardRow({
|
||||||
|
rowId,
|
||||||
|
columns,
|
||||||
|
onRemove,
|
||||||
|
}: {
|
||||||
|
rowId: string;
|
||||||
|
columns: BoardColumnType[];
|
||||||
|
onAddComponent?: () => void;
|
||||||
|
onRemove?: (id: string) => void;
|
||||||
|
}) {
|
||||||
|
const { board, updateBoard } = useBoard();
|
||||||
|
|
||||||
|
const handleAddColumn = () => {
|
||||||
|
updateBoard({
|
||||||
|
parameters: produce(board.parameters, draft => {
|
||||||
|
const rowIndex = draft.rows.findIndex(row => row.id === rowId);
|
||||||
|
const row = draft.rows[rowIndex];
|
||||||
|
|
||||||
|
if (!row) {
|
||||||
|
draft.rows[rowIndex] = { id: uuid(), columns: [] };
|
||||||
|
}
|
||||||
|
row.columns.push({ id: uuid(), component: null });
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Group style={{ height: '100%' }}>
|
||||||
{board?.parameters?.rows?.map((row, rowIndex) => {
|
{columns?.map((column, index) => (
|
||||||
return <BoardRow key={row.id} rowIndex={rowIndex} components={row.components} />;
|
<Fragment key={column.id}>
|
||||||
})}
|
<Panel minSize={300}>
|
||||||
<Row>
|
<BoardColumn {...column} />
|
||||||
<Button variant="outline" onPress={handleAddRow}>
|
</Panel>
|
||||||
|
{index < columns.length - 1 && <Separator />}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
<Box alignSelf="center" padding="3">
|
||||||
|
<Button variant="outline" onPress={handleAddColumn}>
|
||||||
<Icon>
|
<Icon>
|
||||||
<Plus />
|
<Plus />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
</Row>
|
<TooltipTrigger delay={0}>
|
||||||
</Column>
|
<Button variant="outline" onPress={() => onRemove?.(rowId)}>
|
||||||
|
<Icon>
|
||||||
|
<Minus />
|
||||||
|
</Icon>
|
||||||
|
</Button>
|
||||||
|
<Tooltip placement="bottom">Remove row</Tooltip>
|
||||||
|
</TooltipTrigger>
|
||||||
|
</Box>
|
||||||
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function BoardComponent() {
|
function BoardColumn({ id, component }: { id: string; component?: ReactElement }) {
|
||||||
return <Column>hi</Column>;
|
const handleAddComponent = () => {};
|
||||||
}
|
|
||||||
|
|
||||||
function BoardRow({ rowIndex, components }: { rowIndex: number; components: any[] }) {
|
|
||||||
const { board, updateBoard } = useBoard();
|
|
||||||
|
|
||||||
const handleAddComponent = () => {
|
|
||||||
updateBoard({
|
|
||||||
parameters: produce(board.parameters, draft => {
|
|
||||||
if (!draft.rows[rowIndex]) {
|
|
||||||
draft.rows[rowIndex] = { id: uuid(), components: [] };
|
|
||||||
}
|
|
||||||
draft.rows[rowIndex].components.push({ id: uuid(), type: 'text', value: '' });
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Column
|
||||||
{components?.map(component => {
|
marginTop="3"
|
||||||
return <BoardComponent key={component.id} />;
|
marginLeft="3"
|
||||||
})}
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
backgroundColor="3"
|
||||||
|
>
|
||||||
<Button variant="outline" onPress={handleAddComponent}>
|
<Button variant="outline" onPress={handleAddComponent}>
|
||||||
<Icon>
|
<Icon>
|
||||||
<Plus />
|
<Plus />
|
||||||
</Icon>
|
</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
</Row>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { SHARE_ID_REGEX } from '@/lib/constants';
|
|
||||||
import { parseRequest } from '@/lib/request';
|
import { parseRequest } from '@/lib/request';
|
||||||
import { badRequest, json, ok, serverError, unauthorized } from '@/lib/response';
|
import { badRequest, json, ok, serverError, unauthorized } from '@/lib/response';
|
||||||
import { canDeleteBoard, canUpdateBoard, canViewBoard } from '@/permissions';
|
import { canDeleteBoard, canUpdateBoard, canViewBoard } from '@/permissions';
|
||||||
|
|
@ -27,7 +26,7 @@ export async function POST(request: Request, { params }: { params: Promise<{ boa
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
shareId: z.string().regex(SHARE_ID_REGEX).nullable().optional(),
|
parameters: z.object({}).passthrough().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { auth, body, error } = await parseRequest(request, schema);
|
const { auth, body, error } = await parseRequest(request, schema);
|
||||||
|
|
@ -37,14 +36,14 @@ export async function POST(request: Request, { params }: { params: Promise<{ boa
|
||||||
}
|
}
|
||||||
|
|
||||||
const { boardId } = await params;
|
const { boardId } = await params;
|
||||||
const { name, description, shareId } = body;
|
const { name, description, parameters } = body;
|
||||||
|
|
||||||
if (!(await canUpdateBoard(auth, boardId))) {
|
if (!(await canUpdateBoard(auth, boardId))) {
|
||||||
return unauthorized();
|
return unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const board = await updateBoard(boardId, { name, description, shareId });
|
const board = await updateBoard(boardId, { name, description, parameters });
|
||||||
|
|
||||||
return Response.json(board);
|
return Response.json(board);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import type { UseQueryOptions } from '@tanstack/react-query';
|
import type { UseQueryOptions } from '@tanstack/react-query';
|
||||||
|
import type { ReactElement } from 'react';
|
||||||
|
import type { Board as PrismaBoard } from '@/generated/prisma/client';
|
||||||
import type { DATA_TYPE, OPERATORS, ROLES } from './constants';
|
import type { DATA_TYPE, OPERATORS, ROLES } from './constants';
|
||||||
import type { TIME_UNIT } from './date';
|
import type { TIME_UNIT } from './date';
|
||||||
|
|
||||||
|
|
@ -142,6 +144,26 @@ export interface ApiError extends Error {
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BoardData {
|
export interface BoardComponent {
|
||||||
rows: { id: string; name: string; value: number }[];
|
id: string;
|
||||||
|
type: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BoardColumn {
|
||||||
|
id: string;
|
||||||
|
component?: ReactElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BoardRow {
|
||||||
|
id: string;
|
||||||
|
columns: BoardColumn[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BoardParameters {
|
||||||
|
rows?: BoardRow[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Board extends Omit<PrismaBoard, 'parameters'> {
|
||||||
|
parameters: BoardParameters;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue