diff --git a/next-tavla/src/Admin/scenarios/Boards/components/Column/Actions.tsx b/next-tavla/src/Admin/scenarios/Boards/components/Column/Actions.tsx index 0b7b8c475..7bf61fedf 100644 --- a/next-tavla/src/Admin/scenarios/Boards/components/Column/Actions.tsx +++ b/next-tavla/src/Admin/scenarios/Boards/components/Column/Actions.tsx @@ -7,16 +7,19 @@ import { useLink } from '../../hooks/useLink' import { useToast } from '@entur/alert' import classes from './styles.module.css' import { DeleteBoardButton } from '../Delete' +import { SortableColumn } from './SortableColumn' function Actions({ board }: { board: TBoard }) { const link = useLink(board.id) return ( -
- - - - -
+ +
+ + + + +
+
) } diff --git a/next-tavla/src/Admin/scenarios/Boards/components/Column/LastModified.tsx b/next-tavla/src/Admin/scenarios/Boards/components/Column/LastModified.tsx index 3d737c180..b70374ca8 100644 --- a/next-tavla/src/Admin/scenarios/Boards/components/Column/LastModified.tsx +++ b/next-tavla/src/Admin/scenarios/Boards/components/Column/LastModified.tsx @@ -1,7 +1,12 @@ import { formatTimestamp } from 'Admin/utils/time' +import { SortableColumn } from './SortableColumn' function LastModified({ timestamp }: { timestamp?: number }) { - return
{formatTimestamp(timestamp)}
+ return ( + + {formatTimestamp(timestamp)} + + ) } export { LastModified } diff --git a/next-tavla/src/Admin/scenarios/Boards/components/Column/Link.tsx b/next-tavla/src/Admin/scenarios/Boards/components/Column/Link.tsx index 81f05e4d7..5d82b8e83 100644 --- a/next-tavla/src/Admin/scenarios/Boards/components/Column/Link.tsx +++ b/next-tavla/src/Admin/scenarios/Boards/components/Column/Link.tsx @@ -1,8 +1,9 @@ import { useLink } from '../../hooks/useLink' +import { SortableColumn } from './SortableColumn' function Link({ bid }: { bid?: string }) { const link = useLink(bid) - return
{link}
+ return {link} } export { Link } diff --git a/next-tavla/src/Admin/scenarios/Boards/components/Column/Name.tsx b/next-tavla/src/Admin/scenarios/Boards/components/Column/Name.tsx index 782da7fb2..1319447a3 100644 --- a/next-tavla/src/Admin/scenarios/Boards/components/Column/Name.tsx +++ b/next-tavla/src/Admin/scenarios/Boards/components/Column/Name.tsx @@ -1,7 +1,8 @@ import { DEFAULT_BOARD_NAME } from 'Admin/utils/constants' +import { SortableColumn } from './SortableColumn' function Name({ name = DEFAULT_BOARD_NAME }: { name?: string }) { - return
{name}
+ return {name} } export { Name } diff --git a/next-tavla/src/Admin/scenarios/Boards/components/Column/SortableColumn.tsx b/next-tavla/src/Admin/scenarios/Boards/components/Column/SortableColumn.tsx new file mode 100644 index 000000000..b98100524 --- /dev/null +++ b/next-tavla/src/Admin/scenarios/Boards/components/Column/SortableColumn.tsx @@ -0,0 +1,26 @@ +import { TBoardsColumn } from 'Admin/types/boards' +import classes from './styles.module.css' +import { useSortableColumnAttributes } from '../../hooks/useSortableColumnAttributes' + +function SortableColumn({ + column, + children, +}: { + column: TBoardsColumn + children: React.ReactNode +}) { + const { setNodeRef, style } = useSortableColumnAttributes(column) + + return ( +
+ {children} +
+ ) +} + +export { SortableColumn } diff --git a/next-tavla/src/Admin/scenarios/Boards/components/Column/styles.module.css b/next-tavla/src/Admin/scenarios/Boards/components/Column/styles.module.css index 1f5ac3373..6318160d5 100644 --- a/next-tavla/src/Admin/scenarios/Boards/components/Column/styles.module.css +++ b/next-tavla/src/Admin/scenarios/Boards/components/Column/styles.module.css @@ -2,3 +2,10 @@ display: flex; gap: 0.5em; } + +.column { + padding-left: 0.25em; + min-height: 2.25rem; + display: flex; + align-items: center; +} diff --git a/next-tavla/src/Admin/scenarios/Boards/components/TableHeader/ColumnHeader.tsx b/next-tavla/src/Admin/scenarios/Boards/components/TableHeader/ColumnHeader.tsx new file mode 100644 index 000000000..3d8452e2a --- /dev/null +++ b/next-tavla/src/Admin/scenarios/Boards/components/TableHeader/ColumnHeader.tsx @@ -0,0 +1,33 @@ +import { BoardsColumns, TBoardsColumn } from 'Admin/types/boards' +import classes from './styles.module.css' +import { Sort } from '../Sort' +import { Tooltip } from '@entur/tooltip' +import { useSortableColumnAttributes } from '../../hooks/useSortableColumnAttributes' + +function ColumnHeader({ column }: { column: TBoardsColumn }) { + const { attributes, listeners, setNodeRef, style } = + useSortableColumnAttributes(column) + + return ( +
+ +
+ {BoardsColumns[column]} +
+
+ +
+ ) +} + +export { ColumnHeader } diff --git a/next-tavla/src/Admin/scenarios/Boards/components/TableHeader/index.tsx b/next-tavla/src/Admin/scenarios/Boards/components/TableHeader/index.tsx index 770fed810..76c641043 100644 --- a/next-tavla/src/Admin/scenarios/Boards/components/TableHeader/index.tsx +++ b/next-tavla/src/Admin/scenarios/Boards/components/TableHeader/index.tsx @@ -1,31 +1,11 @@ -import { useCallback } from 'react' -import classes from './styles.module.css' import { TBoardsColumn } from 'Admin/types/boards' -import { Sort } from '../Sort' +import { ColumnHeader } from './ColumnHeader' function TableHeader({ columns }: { columns: TBoardsColumn[] }) { - const title = useCallback((column: TBoardsColumn) => { - switch (column) { - case 'name': - return 'Navn på tavle' - case 'url': - return 'Lenke' - case 'actions': - return 'Handlinger' - case 'lastModified': - return 'Sist oppdatert' - default: - return 'Ukjent kolonne' - } - }, []) - return ( <> - {columns.map((column) => ( -
-
{title(column)}
- -
+ {columns.map((column: TBoardsColumn) => ( + ))} ) diff --git a/next-tavla/src/Admin/scenarios/Boards/components/TableHeader/styles.module.css b/next-tavla/src/Admin/scenarios/Boards/components/TableHeader/styles.module.css index 22fdedaa0..3b1d32330 100644 --- a/next-tavla/src/Admin/scenarios/Boards/components/TableHeader/styles.module.css +++ b/next-tavla/src/Admin/scenarios/Boards/components/TableHeader/styles.module.css @@ -3,12 +3,27 @@ align-items: center; gap: 0.5em; border-bottom: var(--colors-blues-blue20) solid; - padding-bottom: 0.5em; margin-bottom: 1em; height: 3em; } .title { + display: flex; + height: 3em; + align-items: center; font-weight: 500; - color: var(--colors-brand-lavender); + color: var(--border-color); + padding: 0 0.25rem; + border-top-right-radius: 0.25em; + border-top-left-radius: 0.25em; +} + +.title:hover { + cursor: grab; + background-color: var(--tertiary-background-color); +} + +.title:active { + cursor: grabbing; + background-color: var(--tertiary-background-color); } diff --git a/next-tavla/src/Admin/scenarios/Boards/components/TableRows/index.tsx b/next-tavla/src/Admin/scenarios/Boards/components/TableRows/index.tsx index 6fbfbc7c7..4130d932d 100644 --- a/next-tavla/src/Admin/scenarios/Boards/components/TableRows/index.tsx +++ b/next-tavla/src/Admin/scenarios/Boards/components/TableRows/index.tsx @@ -1,26 +1,26 @@ -import { TBoardsColumn } from 'Admin/types/boards' import { useBoardsSettings } from '../../utils/context' import { useSortBoardFunction } from '../../hooks/useSortBoardFunction' import { DEFAULT_BOARD_NAME } from 'Admin/utils/constants' import { Column } from '../Column' import { Fragment } from 'react' import { TBoard } from 'types/settings' +import { TBoardsColumn } from 'Admin/types/boards' function TableRows() { - const settings = useBoardsSettings() + const { boards, columns, search } = useBoardsSettings() const sortFunction = useSortBoardFunction() - const filter = new RegExp(settings.search, 'i') + const filter = new RegExp(search, 'i') return ( <> - {settings.boards + {boards .filter((board: TBoard) => filter.test(board?.meta?.title ?? DEFAULT_BOARD_NAME), ) .sort(sortFunction) .map((board: TBoard) => ( - {settings.columns.map((column: TBoardsColumn) => ( + {columns.map((column: TBoardsColumn) => ( + + + + + + +
+
+ + Velg kolonner + + + + + +
+
+ {Object.entries(BoardsColumns).map(([column]) => ( + + dispatch({ + type: 'toggleColumn', + column: column as TBoardsColumn, + }) + } + > + {BoardsColumns[column as TBoardsColumn]} + + ))} +
+
+
+ + ) +} + +export { ToggleBoardsColumns } diff --git a/next-tavla/src/Admin/scenarios/Boards/components/ToggleBoardsColumns/styles.module.css b/next-tavla/src/Admin/scenarios/Boards/components/ToggleBoardsColumns/styles.module.css new file mode 100644 index 000000000..edaa20709 --- /dev/null +++ b/next-tavla/src/Admin/scenarios/Boards/components/ToggleBoardsColumns/styles.module.css @@ -0,0 +1,16 @@ +.boardListOptions { + margin-left: auto; +} + +.popoverHeading { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.contentList { + padding: 0.5rem; + display: flex; + flex-direction: column; + gap: 0.25rem; +} diff --git a/next-tavla/src/Admin/scenarios/Boards/hooks/useSortableColumnAttributes.ts b/next-tavla/src/Admin/scenarios/Boards/hooks/useSortableColumnAttributes.ts new file mode 100644 index 000000000..f8f01a4c3 --- /dev/null +++ b/next-tavla/src/Admin/scenarios/Boards/hooks/useSortableColumnAttributes.ts @@ -0,0 +1,37 @@ +import { CSSProperties } from 'react' +import { useSortable } from '@dnd-kit/sortable' +import { CSS } from '@dnd-kit/utilities' + +export function useSortableColumnAttributes(column: string) { + const { attributes, listeners, transform, transition, active, setNodeRef } = + useSortable({ id: column }) + + const otherColumnActive = active && active.id !== column + const thisColumnActive = active && active.id === column + + const activeStyle = + thisColumnActive && + ({ + backgroundColor: 'var(--main-background-color)', + } as CSSProperties) + + const style = { + transform: CSS.Translate.toString(transform), + transition: transition, + zIndex: thisColumnActive ? 10 : 0, + opacity: otherColumnActive ? 0.5 : 1, + ...activeStyle, + } + + if (thisColumnActive) style.backgroundColor = 'var(--main-background-color)' + + return { + style, + attributes, + listeners, + transform, + transition, + active, + setNodeRef, + } +} diff --git a/next-tavla/src/Admin/scenarios/Boards/index.tsx b/next-tavla/src/Admin/scenarios/Boards/index.tsx index d610f350a..1d8674f71 100644 --- a/next-tavla/src/Admin/scenarios/Boards/index.tsx +++ b/next-tavla/src/Admin/scenarios/Boards/index.tsx @@ -8,6 +8,7 @@ import { SettingsContext, SettingsDispatchContext, useBoardsSettings, + useBoardsSettingsDispatch, } from './utils/context' import { Search } from './components/Search' import { isEmpty } from 'lodash' @@ -15,6 +16,27 @@ import { IllustratedInfo } from 'Admin/components/IllustratedInfo' import { TableHeader } from './components/TableHeader' import { TableRows } from './components/TableRows' import { CreateBoard } from 'Admin/components/CreateBoard' +import { ToggleBoardsColumns } from './components/ToggleBoardsColumns' +import { TBoardsColumn } from 'Admin/types/boards' +import { + useSensors, + useSensor, + PointerSensor, + KeyboardSensor, + DragEndEvent, + DndContext, + closestCenter, +} from '@dnd-kit/core' +import { + sortableKeyboardCoordinates, + arrayMove, + SortableContext, + horizontalListSortingStrategy, +} from '@dnd-kit/sortable' +import { + restrictToHorizontalAxis, + restrictToWindowEdges, +} from '@dnd-kit/modifiers' function Boards({ boards }: { boards: TBoard[] }) { const [settings, dispatch] = useReducer(settingsReducer, { @@ -32,7 +54,10 @@ function Boards({ boards }: { boards: TBoard[] }) { Mine Tavler - +
+ + +
@@ -41,9 +66,17 @@ function Boards({ boards }: { boards: TBoard[] }) { } function BoardTable() { - const settings = useBoardsSettings() + const { boards, columns } = useBoardsSettings() + const dispatch = useBoardsSettingsDispatch() - if (isEmpty(settings.boards)) + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }), + ) + + if (isEmpty(boards)) return ( ) + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event + + if (active.id !== over?.id) { + const oldIndex = columns.indexOf(active.id as TBoardsColumn) + const newIndex = columns.indexOf(over?.id as TBoardsColumn) + const newOrder = arrayMove(columns, oldIndex, newIndex) + dispatch({ type: 'setColumns', columns: newOrder }) + } + } + return (
- - + + + + + +
) } diff --git a/next-tavla/src/Admin/scenarios/Boards/styles.module.css b/next-tavla/src/Admin/scenarios/Boards/styles.module.css index 7a2f69628..1a1d6c1ee 100644 --- a/next-tavla/src/Admin/scenarios/Boards/styles.module.css +++ b/next-tavla/src/Admin/scenarios/Boards/styles.module.css @@ -14,3 +14,10 @@ display: grid; align-items: center; } + +.actionRow { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1em; +} diff --git a/next-tavla/src/Admin/scenarios/Boards/utils/reducer.ts b/next-tavla/src/Admin/scenarios/Boards/utils/reducer.ts index 79cd14c87..7761a6672 100644 --- a/next-tavla/src/Admin/scenarios/Boards/utils/reducer.ts +++ b/next-tavla/src/Admin/scenarios/Boards/utils/reducer.ts @@ -1,9 +1,12 @@ import { TBoards, TBoardsColumn, TSort } from 'Admin/types/boards' +import { xor } from 'lodash' export type Action = | { type: 'setSearch'; search: string } | { type: 'setSort'; sort: { type: TSort; column: TBoardsColumn } } | { type: 'deleteBoard'; bid: string } + | { type: 'toggleColumn'; column: TBoardsColumn } + | { type: 'setColumns'; columns: TBoardsColumn[] } export function settingsReducer(board: TBoards, action: Action): TBoards { switch (action.type) { @@ -16,5 +19,15 @@ export function settingsReducer(board: TBoards, action: Action): TBoards { ...board, boards: board.boards.filter((b) => b.id !== action.bid), } + case 'toggleColumn': + return { + ...board, + columns: xor(board.columns, [action.column]), + } + case 'setColumns': + return { + ...board, + columns: action.columns, + } } } diff --git a/next-tavla/src/Admin/types/boards.ts b/next-tavla/src/Admin/types/boards.ts index 321e8e157..045119718 100644 --- a/next-tavla/src/Admin/types/boards.ts +++ b/next-tavla/src/Admin/types/boards.ts @@ -2,7 +2,14 @@ import { TBoard } from 'types/settings' export type TSort = 'none' | 'ascending' | 'descending' -export type TBoardsColumn = 'name' | 'url' | 'actions' | 'lastModified' +export const BoardsColumns = { + name: 'Tavlenavn', + url: 'Lenke', + actions: 'Handlinger', + lastModified: 'Sist oppdatert', +} as const + +export type TBoardsColumn = keyof typeof BoardsColumns export const SortableColumns = ['name', 'lastModified'] as const diff --git a/next-tavla/src/Shared/styles/global.css b/next-tavla/src/Shared/styles/global.css index 3f5a40d3f..1f7b7703b 100644 --- a/next-tavla/src/Shared/styles/global.css +++ b/next-tavla/src/Shared/styles/global.css @@ -41,3 +41,9 @@ h3 { flex-direction: column; gap: 1rem; } + +.flexRow { + display: flex; + flex-direction: row; + gap: 1rem; +} diff --git a/next-tavla/src/Shared/styles/spacing.css b/next-tavla/src/Shared/styles/spacing.css index 6922d103d..83e02d705 100644 --- a/next-tavla/src/Shared/styles/spacing.css +++ b/next-tavla/src/Shared/styles/spacing.css @@ -1,3 +1,83 @@ +.m-1 { + margin: 0.5em; +} + +.m-2 { + margin: 1em; +} + +.m-3 { + margin: 1.5em; +} + +.m-4 { + margin: 2em; +} + +.mt-1 { + margin-top: 0.5em; +} + +.mt-2 { + margin-top: 1em; +} + +.mt-3 { + margin-top: 1.5em; +} + +.mt-4 { + margin-top: 2em; +} + +.ml-1 { + margin-left: 0.5em; +} + +.ml-2 { + margin-left: 1em; +} + +.ml-3 { + margin-left: 1.5em; +} + +.ml-4 { + margin-left: 2em; +} + +.mr-1 { + margin-right: 0.5em; +} + +.mr-2 { + margin-right: 1em; +} + +.mr-3 { + margin-right: 1.5em; +} + +.mr-4 { + margin-right: 2em; +} + +.mb-1 { + margin-bottom: 0.5em; +} + +.mb-2 { + margin-bottom: 1em; +} + +.mb-3 { + margin-bottom: 1.5em; +} + +.mb-4 { + margin-bottom: 2em; +} + .p-1 { padding: 0.5em; }