Tom Butcher 044abf32a8 Refactor Dashboard Overview Components for Enhanced Layout and Functionality
- Introduced DashboardOverviewPage and DashboardOverviewFlexColumns components to streamline the layout and management of dashboard sections, improving overall organization and usability.
- Replaced individual collapsible sections in Finance, Inventory, Production, and Sales overviews with a unified structure, enhancing maintainability and consistency across the dashboard.
- Added ActionsButton component for improved action handling and keyboard shortcuts, enhancing user interaction capabilities.
- Updated styles in App.css to support new drag-and-drop functionality and layout features, ensuring a cohesive visual experience.
- Refactored existing components to utilize the new structure, improving code clarity and reducing redundancy.
2026-08-25 01:19:17 +01:00

214 lines
5.9 KiB
JavaScript

import { useCallback, useContext } from 'react'
import { ApiServerContext } from '../context/ApiServerContext'
export const mergeOrder = (saved = [], defaults = []) => {
const defaultSet = new Set(defaults)
const kept = saved.filter((key) => defaultSet.has(key))
const keptSet = new Set(kept)
const appended = defaults.filter((key) => !keptSet.has(key))
return [...kept, ...appended]
}
export const COLUMN_PROPORTION_STEP = 5
const MIN_COLUMN_PROPORTION = 5
export const normalizeColumnProportions = (count, saved) => {
if (count <= 0) return []
if (count === 1) return [100]
if (Array.isArray(saved) && saved.length === count) {
return saved.map((value) => {
const numeric = Number(value)
if (!Number.isFinite(numeric)) return MIN_COLUMN_PROPORTION
return Math.max(
MIN_COLUMN_PROPORTION,
Math.round(numeric / COLUMN_PROPORTION_STEP) * COLUMN_PROPORTION_STEP
)
})
}
const base =
Math.floor(100 / count / COLUMN_PROPORTION_STEP) * COLUMN_PROPORTION_STEP
const result = Array(count).fill(Math.max(MIN_COLUMN_PROPORTION, base))
let remainder = 100 - result.reduce((sum, value) => sum + value, 0)
let i = 0
while (remainder >= COLUMN_PROPORTION_STEP) {
result[i % count] += COLUMN_PROPORTION_STEP
remainder -= COLUMN_PROPORTION_STEP
i += 1
}
return result
}
const asObject = (value) =>
value && typeof value === 'object' && !Array.isArray(value) ? value : {}
const asStringArray = (value) =>
Array.isArray(value) ? value.filter((key) => typeof key === 'string') : []
const collectKnownLeaves = (defaultOrder, defaultFlexColumns) => {
const leaves = new Set()
for (const key of defaultOrder) {
if (defaultFlexColumns[key]) {
for (const child of defaultFlexColumns[key]) {
leaves.add(child)
}
} else {
leaves.add(key)
}
}
return leaves
}
export const mergeLayout = (saved, defaultLayout) => {
const defaultOrder = asStringArray(defaultLayout?.sectionOrder)
const defaultFlexColumns = Object.fromEntries(
Object.entries(asObject(defaultLayout?.flexColumns)).map(([id, items]) => [
id,
asStringArray(items)
])
)
const savedOrder = asStringArray(saved?.sectionOrder)
const savedFlexColumns = Object.fromEntries(
Object.entries(asObject(saved?.flexColumns)).map(([id, items]) => [
id,
asStringArray(items)
])
)
const savedStats =
saved?.statsOrder &&
typeof saved.statsOrder === 'object' &&
!Array.isArray(saved.statsOrder)
? saved.statsOrder
: {}
const savedVisibility =
saved?.statsVisibility &&
typeof saved.statsVisibility === 'object' &&
!Array.isArray(saved.statsVisibility)
? saved.statsVisibility
: {}
const knownLeaves = collectKnownLeaves(defaultOrder, defaultFlexColumns)
const defaultGroupIds = new Set(Object.keys(defaultFlexColumns))
const userGroupIds = Object.keys(savedFlexColumns).filter(
(id) => !defaultGroupIds.has(id) && !knownLeaves.has(id)
)
const userGroupIdSet = new Set(userGroupIds)
const isGroupId = (key) => defaultGroupIds.has(key) || userGroupIdSet.has(key)
const placedLeaves = new Set()
const takeLeaves = (keys) => {
const result = []
for (const key of keys || []) {
if (knownLeaves.has(key) && !placedLeaves.has(key)) {
placedLeaves.add(key)
result.push(key)
}
}
return result
}
const sectionOrder = []
const flexColumns = {}
const addGroup = (id, childKeys) => {
flexColumns[id] = childKeys
if (!sectionOrder.includes(id)) {
sectionOrder.push(id)
}
}
const hasSavedStructure =
savedOrder.length > 0 || Object.keys(savedFlexColumns).length > 0
if (hasSavedStructure) {
for (const key of savedOrder) {
if (knownLeaves.has(key) && !placedLeaves.has(key)) {
placedLeaves.add(key)
sectionOrder.push(key)
} else if (isGroupId(key)) {
addGroup(
key,
takeLeaves(savedFlexColumns[key] ?? defaultFlexColumns[key] ?? [])
)
}
}
}
for (const key of defaultOrder) {
if (knownLeaves.has(key) && !placedLeaves.has(key)) {
placedLeaves.add(key)
sectionOrder.push(key)
} else if (defaultGroupIds.has(key)) {
const remaining = takeLeaves(
mergeOrder(savedFlexColumns[key] || [], defaultFlexColumns[key])
)
if (sectionOrder.includes(key)) {
flexColumns[key] = [...(flexColumns[key] || []), ...remaining]
} else if (remaining.length > 0 || !hasSavedStructure) {
addGroup(key, remaining)
}
}
}
for (const id of userGroupIds) {
if (!sectionOrder.includes(id)) {
addGroup(id, takeLeaves(savedFlexColumns[id]))
}
}
for (const leaf of knownLeaves) {
if (!placedLeaves.has(leaf)) {
placedLeaves.add(leaf)
sectionOrder.push(leaf)
}
}
const flexColumnProportions = Object.fromEntries(
Object.entries(asObject(saved?.flexColumnProportions)).map(
([id, value]) => [
id,
Array.isArray(value)
? value.filter(
(item) => typeof item === 'number' && Number.isFinite(item)
)
: []
]
)
)
return {
sectionOrder,
flexColumns,
statsOrder: savedStats,
statsVisibility: savedVisibility,
sectionHeights: asObject(saved?.sectionHeights),
flexColumnProportions
}
}
const usePageLayout = (
pageName,
defaultLayout = {
sectionOrder: [],
flexColumns: {},
statsOrder: {},
statsVisibility: {}
}
) => {
const { userSettings, updateUserSettings } = useContext(ApiServerContext)
const saved = userSettings.pageLayout?.[pageName]
const layout = mergeLayout(saved, defaultLayout)
const updatePageLayout = useCallback(
(value) => {
updateUserSettings('pageLayout', pageName, value)
},
[pageName, updateUserSettings]
)
return [layout, updatePageLayout]
}
export default usePageLayout