mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 22:04:56 +00:00
Implement data export and filtering modules with single-function files
- Added CSV export functionality with escape handling. - Implemented JSON export functionality. - Created utility functions for retrieving column labels and row values. - Established a filtering system with state management and filter application. - Refactored sorting logic into dedicated modules for better maintainability. - Deprecated old filtering and sorting files, redirecting to new module structure. - Introduced form field builders and validation utilities, also refactored into single-function files.
This commit is contained in:
@@ -1,13 +1,66 @@
|
||||
import React from 'react'
|
||||
import { classNames } from '../utils/classNames'
|
||||
|
||||
export type SkeletonVariant = 'text' | 'rectangular' | 'circular'
|
||||
export type SkeletonVariant = 'text' | 'rectangular' | 'circular' | 'rounded'
|
||||
export type SkeletonAnimation = 'pulse' | 'wave' | false
|
||||
|
||||
export interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
export interface SkeletonProps extends React.HTMLAttributes<HTMLSpanElement> {
|
||||
/** The type of skeleton shape */
|
||||
variant?: SkeletonVariant
|
||||
/** Width of the skeleton (accepts CSS units) */
|
||||
width?: string | number
|
||||
/** Height of the skeleton (accepts CSS units) */
|
||||
height?: string | number
|
||||
/** Animation type or false to disable */
|
||||
animation?: SkeletonAnimation
|
||||
/** If true, skeleton takes up full width of parent */
|
||||
fullWidth?: boolean
|
||||
/** Custom component to render as skeleton root */
|
||||
component?: React.ElementType
|
||||
}
|
||||
|
||||
export const Skeleton: React.FC<SkeletonProps> = ({ variant = 'text', width, height, className = '', ...props }) => (
|
||||
<div className={`skeleton skeleton--${variant} ${className}`} style={{ width, height }} {...props} />
|
||||
)
|
||||
/**
|
||||
* Loading placeholder with shimmer animation
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* // Basic text skeleton
|
||||
* <Skeleton variant="text" width={210} />
|
||||
*
|
||||
* // Avatar placeholder
|
||||
* <Skeleton variant="circular" width={40} height={40} />
|
||||
*
|
||||
* // Card image placeholder
|
||||
* <Skeleton variant="rectangular" height={118} />
|
||||
* ```
|
||||
*/
|
||||
export const Skeleton: React.FC<SkeletonProps> = ({
|
||||
variant = 'text',
|
||||
width,
|
||||
height,
|
||||
animation = 'pulse',
|
||||
fullWidth = false,
|
||||
component: Component = 'span',
|
||||
className,
|
||||
style,
|
||||
...props
|
||||
}) => {
|
||||
const rootClassName = classNames(
|
||||
'fakemui-skeleton',
|
||||
`fakemui-skeleton--${variant}`,
|
||||
animation && `fakemui-skeleton--${animation}`,
|
||||
fullWidth && 'fakemui-skeleton--full-width',
|
||||
className
|
||||
)
|
||||
|
||||
const rootStyle: React.CSSProperties = {
|
||||
...style,
|
||||
width: fullWidth ? '100%' : width,
|
||||
height,
|
||||
}
|
||||
|
||||
return <Component className={rootClassName} style={rootStyle} {...props} />
|
||||
}
|
||||
|
||||
export default Skeleton
|
||||
|
||||
|
||||
9
fakemui/icons/ArrowLeft.tsx
Normal file
9
fakemui/icons/ArrowLeft.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const ArrowLeft = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<line x1="216" y1="128" x2="40" y2="128" />
|
||||
<polyline points="112 56 40 128 112 200" />
|
||||
</Icon>
|
||||
)
|
||||
9
fakemui/icons/ArrowRight.tsx
Normal file
9
fakemui/icons/ArrowRight.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const ArrowRight = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<line x1="40" y1="128" x2="216" y2="128" />
|
||||
<polyline points="144 56 216 128 144 200" />
|
||||
</Icon>
|
||||
)
|
||||
9
fakemui/icons/BookOpen.tsx
Normal file
9
fakemui/icons/BookOpen.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const BookOpen = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<path d="M128 88a32 32 0 0 0-32-32H48a8 8 0 0 0-8 8v136a8 8 0 0 0 8 8h48a32 32 0 0 1 32 32" />
|
||||
<path d="M128 88a32 32 0 0 1 32-32h48a8 8 0 0 1 8 8v136a8 8 0 0 1-8 8h-48a32 32 0 0 0-32 32" />
|
||||
</Icon>
|
||||
)
|
||||
10
fakemui/icons/Broom.tsx
Normal file
10
fakemui/icons/Broom.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const Broom = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<path d="M40 216h40l24-56" />
|
||||
<path d="M64 200l64-96 40 40-56 80" />
|
||||
<path d="M128 104l20-20a56 56 0 0 1 79.2 79.2l-20 20" />
|
||||
</Icon>
|
||||
)
|
||||
15
fakemui/icons/Buildings.tsx
Normal file
15
fakemui/icons/Buildings.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const Buildings = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<rect x="32" y="56" width="80" height="168" rx="8" />
|
||||
<rect x="144" y="96" width="80" height="128" rx="8" />
|
||||
<line x1="112" y1="104" x2="144" y2="104" />
|
||||
<line x1="56" y1="88" x2="88" y2="88" />
|
||||
<line x1="56" y1="120" x2="88" y2="120" />
|
||||
<line x1="56" y1="152" x2="88" y2="152" />
|
||||
<line x1="168" y1="128" x2="200" y2="128" />
|
||||
<line x1="168" y1="160" x2="200" y2="160" />
|
||||
</Icon>
|
||||
)
|
||||
8
fakemui/icons/CaretDown.tsx
Normal file
8
fakemui/icons/CaretDown.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const CaretDown = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<polyline points="208 96 128 176 48 96" />
|
||||
</Icon>
|
||||
)
|
||||
8
fakemui/icons/CaretRight.tsx
Normal file
8
fakemui/icons/CaretRight.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const CaretRight = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<polyline points="96 48 176 128 96 208" />
|
||||
</Icon>
|
||||
)
|
||||
8
fakemui/icons/ChatCircle.tsx
Normal file
8
fakemui/icons/ChatCircle.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const ChatCircle = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<path d="M128 24a104 104 0 0 0-91.2 153.8L24 224l46.2-12.8A104 104 0 1 0 128 24Z" />
|
||||
</Icon>
|
||||
)
|
||||
9
fakemui/icons/CheckCircle.tsx
Normal file
9
fakemui/icons/CheckCircle.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const CheckCircle = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<circle cx="128" cy="128" r="96" />
|
||||
<polyline points="88 136 112 160 168 104" />
|
||||
</Icon>
|
||||
)
|
||||
13
fakemui/icons/ColumnResize.tsx
Normal file
13
fakemui/icons/ColumnResize.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const ColumnResize = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<rect x="48" y="40" width="160" height="176" rx="8" />
|
||||
<line x1="128" y1="40" x2="128" y2="216" strokeDasharray="8 8" />
|
||||
<polyline points="104,88 80,128 104,168" />
|
||||
<polyline points="152,88 176,128 152,168" />
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export default ColumnResize
|
||||
8
fakemui/icons/Crown.tsx
Normal file
8
fakemui/icons/Crown.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const Crown = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<path d="M232 80l-56 120H80L24 80l56 40 48-72 48 72Z" />
|
||||
</Icon>
|
||||
)
|
||||
12
fakemui/icons/Csv.tsx
Normal file
12
fakemui/icons/Csv.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const Csv = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<path d="M200,224H56a8,8,0,0,1-8-8V40a8,8,0,0,1,8-8h96l56,56V216A8,8,0,0,1,200,224Z" />
|
||||
<polyline points="152,32 152,88 208,88" />
|
||||
<text x="76" y="168" fontSize="48" fontFamily="monospace" fill="currentColor" stroke="none">CSV</text>
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export default Csv
|
||||
9
fakemui/icons/Envelope.tsx
Normal file
9
fakemui/icons/Envelope.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const Envelope = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<rect x="24" y="56" width="208" height="144" rx="8" />
|
||||
<polyline points="24 56 128 144 232 56" />
|
||||
</Icon>
|
||||
)
|
||||
10
fakemui/icons/Export.tsx
Normal file
10
fakemui/icons/Export.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const Export = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<polyline points="88 96 128 56 168 96" />
|
||||
<line x1="128" y1="152" x2="128" y2="56" />
|
||||
<path d="M216 152v48a8 8 0 0 1-8 8H48a8 8 0 0 1-8-8v-48" />
|
||||
</Icon>
|
||||
)
|
||||
8
fakemui/icons/Funnel.tsx
Normal file
8
fakemui/icons/Funnel.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const Funnel = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<path d="M40 48h176l-64 80v72l-48 24v-96Z" />
|
||||
</Icon>
|
||||
)
|
||||
9
fakemui/icons/Gear.tsx
Normal file
9
fakemui/icons/Gear.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const Gear = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<circle cx="128" cy="128" r="40" />
|
||||
<path d="M130.1 32H126l-8 24.7a72 72 0 0 0-33.2 19.2L56 67.3 41.4 93l20.4 15.5a72.2 72.2 0 0 0 0 38.9L41.4 163l14.6 25.7 28.8-8.6a72 72 0 0 0 33.2 19.2l8 24.7h4.1l4-.1 8-24.7a72 72 0 0 0 33.2-19.2l28.8 8.6 14.6-25.7-20.4-15.5a72.2 72.2 0 0 0 0-38.9l20.4-15.5L204 67.3l-28.8 8.6a72 72 0 0 0-33.2-19.2Z" />
|
||||
</Icon>
|
||||
)
|
||||
8
fakemui/icons/GithubLogo.tsx
Normal file
8
fakemui/icons/GithubLogo.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const GithubLogo = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<path d="M119.8 215.6c0-9.8-.2-41.4-.2-41.4a24 24 0 0 0-7-17c33.6-3.8 68.6-16.7 68.6-74.5a57.6 57.6 0 0 0-15.6-40.3c1.6-3.8 6.8-19.2-1.4-40-12.8-4.2-42.2 16-42.2 16a142.4 142.4 0 0 0-76.8 0S16.2 38.4 3.4 42.6c-8.2 20.8-3 36.2-1.4 40A57.6 57.6 0 0 0-13.6 123c0 57.6 35 70.7 68.4 74.5a23.6 23.6 0 0 0-7 14.6s-16.4 6-31.4-4.8c-17.2-12.4-23.6-39.8-40.2-39.8" />
|
||||
</Icon>
|
||||
)
|
||||
8
fakemui/icons/House.tsx
Normal file
8
fakemui/icons/House.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const House = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<path d="M219.3 108.3l-80-80a16 16 0 0 0-22.6 0l-80 80A15.9 15.9 0 0 0 32 120v96a8 8 0 0 0 8 8h64a8 8 0 0 0 8-8v-56h32v56a8 8 0 0 0 8 8h64a8 8 0 0 0 8-8v-96a15.9 15.9 0 0 0-4.7-11.7Z" />
|
||||
</Icon>
|
||||
)
|
||||
9
fakemui/icons/MagnifyingGlass.tsx
Normal file
9
fakemui/icons/MagnifyingGlass.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const MagnifyingGlass = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<circle cx="112" cy="112" r="80" />
|
||||
<line x1="168" y1="168" x2="224" y2="224" />
|
||||
</Icon>
|
||||
)
|
||||
13
fakemui/icons/Palette.tsx
Normal file
13
fakemui/icons/Palette.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const Palette = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<path d="M200.8 53.9A103.4 103.4 0 0 0 128 24a104 104 0 0 0 0 208c52.9 0 88-40 88-88 0-22.1-17.9-40-40-40h-32a24 24 0 0 1 0-48" />
|
||||
<circle cx="80" cy="120" r="16" fill="currentColor" />
|
||||
<circle cx="120" cy="80" r="16" fill="currentColor" />
|
||||
<circle cx="160" cy="80" r="16" fill="currentColor" />
|
||||
<circle cx="80" cy="168" r="16" fill="currentColor" />
|
||||
<circle cx="128" cy="192" r="16" fill="currentColor" />
|
||||
</Icon>
|
||||
)
|
||||
9
fakemui/icons/Power.tsx
Normal file
9
fakemui/icons/Power.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const Power = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<line x1="128" y1="48" x2="128" y2="128" />
|
||||
<path d="M176 56a96 96 0 1 1-96 0" fill="none" />
|
||||
</Icon>
|
||||
)
|
||||
14
fakemui/icons/RowSelect.tsx
Normal file
14
fakemui/icons/RowSelect.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const RowSelect = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<rect x="40" y="64" width="176" height="48" rx="4" />
|
||||
<rect x="40" y="128" width="176" height="48" rx="4" fill="currentColor" fillOpacity="0.2" />
|
||||
<line x1="56" y1="152" x2="72" y2="152" />
|
||||
<polyline points="60,148 64,156 76,144" />
|
||||
<line x1="88" y1="152" x2="200" y2="152" />
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export default RowSelect
|
||||
15
fakemui/icons/SelectAll.tsx
Normal file
15
fakemui/icons/SelectAll.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const SelectAll = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<rect x="40" y="40" width="176" height="176" rx="8" />
|
||||
<polyline points="80,128 112,160 176,96" />
|
||||
<rect x="56" y="56" width="40" height="40" rx="4" />
|
||||
<rect x="160" y="56" width="40" height="40" rx="4" />
|
||||
<rect x="56" y="160" width="40" height="40" rx="4" />
|
||||
<rect x="160" y="160" width="40" height="40" rx="4" />
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export default SelectAll
|
||||
10
fakemui/icons/SignIn.tsx
Normal file
10
fakemui/icons/SignIn.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const SignIn = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<polyline points="94 170 136 128 94 86" />
|
||||
<line x1="24" y1="128" x2="136" y2="128" />
|
||||
<path d="M136 40h72a8 8 0 0 1 8 8v160a8 8 0 0 1-8 8h-72" />
|
||||
</Icon>
|
||||
)
|
||||
10
fakemui/icons/SignOut.tsx
Normal file
10
fakemui/icons/SignOut.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const SignOut = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<polyline points="174 86 216 128 174 170" />
|
||||
<line x1="104" y1="128" x2="216" y2="128" />
|
||||
<path d="M104 216H48a8 8 0 0 1-8-8V48a8 8 0 0 1 8-8h56" />
|
||||
</Icon>
|
||||
)
|
||||
14
fakemui/icons/SortAscending.tsx
Normal file
14
fakemui/icons/SortAscending.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const SortAscending = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<line x1="128" y1="40" x2="128" y2="216" />
|
||||
<polyline points="56,112 128,40 200,112" />
|
||||
<line x1="40" y1="176" x2="88" y2="176" />
|
||||
<line x1="40" y1="208" x2="104" y2="208" />
|
||||
<line x1="40" y1="144" x2="72" y2="144" />
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export default SortAscending
|
||||
14
fakemui/icons/SortDescending.tsx
Normal file
14
fakemui/icons/SortDescending.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const SortDescending = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<line x1="128" y1="40" x2="128" y2="216" />
|
||||
<polyline points="56,144 128,216 200,144" />
|
||||
<line x1="40" y1="48" x2="88" y2="48" />
|
||||
<line x1="40" y1="80" x2="104" y2="80" />
|
||||
<line x1="40" y1="112" x2="72" y2="112" />
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export default SortDescending
|
||||
9
fakemui/icons/Sparkle.tsx
Normal file
9
fakemui/icons/Sparkle.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const Sparkle = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<path d="M80 36l12.8 38.4a8 8 0 0 0 5.1 5.1L136 92l-38.1 12.5a8 8 0 0 0-5.1 5.1L80 148l-12.8-38.4a8 8 0 0 0-5.1-5.1L24 92l38.1-12.5a8 8 0 0 0 5.1-5.1Z" />
|
||||
<path d="M168 160l9.6 28.8a8 8 0 0 0 5.1 5.1L212 204l-29.3 10a8 8 0 0 0-5.1 5.1L168 248l-9.6-28.9a8 8 0 0 0-5.1-5.1L124 204l29.3-10a8 8 0 0 0 5.1-5.1Z" />
|
||||
</Icon>
|
||||
)
|
||||
11
fakemui/icons/SquaresFour.tsx
Normal file
11
fakemui/icons/SquaresFour.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const SquaresFour = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<rect x="32" y="32" width="72" height="72" rx="8" />
|
||||
<rect x="152" y="32" width="72" height="72" rx="8" />
|
||||
<rect x="32" y="152" width="72" height="72" rx="8" />
|
||||
<rect x="152" y="152" width="72" height="72" rx="8" />
|
||||
</Icon>
|
||||
)
|
||||
11
fakemui/icons/Table.tsx
Normal file
11
fakemui/icons/Table.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const Table = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<rect x="32" y="48" width="192" height="160" rx="8" />
|
||||
<line x1="32" y1="96" x2="224" y2="96" />
|
||||
<line x1="32" y1="144" x2="224" y2="144" />
|
||||
<line x1="96" y1="96" x2="96" y2="208" />
|
||||
</Icon>
|
||||
)
|
||||
15
fakemui/icons/TableCells.tsx
Normal file
15
fakemui/icons/TableCells.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const TableCells = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<rect x="40" y="40" width="176" height="176" rx="8" />
|
||||
<line x1="40" y1="88" x2="216" y2="88" />
|
||||
<line x1="40" y1="136" x2="216" y2="136" />
|
||||
<line x1="40" y1="184" x2="216" y2="184" />
|
||||
<line x1="104" y1="88" x2="104" y2="216" />
|
||||
<line x1="168" y1="88" x2="168" y2="216" />
|
||||
</Icon>
|
||||
)
|
||||
|
||||
export default TableCells
|
||||
9
fakemui/icons/Tree.tsx
Normal file
9
fakemui/icons/Tree.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const Tree = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<line x1="128" y1="232" x2="128" y2="88" />
|
||||
<polygon points="128 24 48 120 88 120 48 200 208 200 168 120 208 120 128 24" fill="none" />
|
||||
</Icon>
|
||||
)
|
||||
9
fakemui/icons/TrendUp.tsx
Normal file
9
fakemui/icons/TrendUp.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const TrendUp = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<polyline points="232 56 136 152 96 112 24 184" />
|
||||
<polyline points="232 120 232 56 168 56" />
|
||||
</Icon>
|
||||
)
|
||||
10
fakemui/icons/UserCircle.tsx
Normal file
10
fakemui/icons/UserCircle.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const UserCircle = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<circle cx="128" cy="128" r="96" />
|
||||
<circle cx="128" cy="120" r="40" />
|
||||
<path d="M63.8 199.4a72 72 0 0 1 128.4 0" fill="none" />
|
||||
</Icon>
|
||||
)
|
||||
11
fakemui/icons/UserPlus.tsx
Normal file
11
fakemui/icons/UserPlus.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const UserPlus = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<circle cx="108" cy="100" r="60" />
|
||||
<path d="M22.2 200c21.6-38.6 62.8-64 85.8-64s64.2 25.4 85.8 64" />
|
||||
<line x1="200" y1="40" x2="200" y2="96" />
|
||||
<line x1="172" y1="68" x2="228" y2="68" />
|
||||
</Icon>
|
||||
)
|
||||
11
fakemui/icons/Users.tsx
Normal file
11
fakemui/icons/Users.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const Users = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<circle cx="84" cy="108" r="52" />
|
||||
<path d="M10.2 200c20.8-37.2 60.2-60 93.8-60s73 22.8 93.8 60" />
|
||||
<circle cx="172" cy="108" r="52" />
|
||||
<path d="M172 152c33.6 0 73 22.8 93.8 60" />
|
||||
</Icon>
|
||||
)
|
||||
10
fakemui/icons/XCircle.tsx
Normal file
10
fakemui/icons/XCircle.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import { Icon, IconProps } from './Icon'
|
||||
|
||||
export const XCircle = (props: IconProps) => (
|
||||
<Icon {...props}>
|
||||
<circle cx="128" cy="128" r="96" />
|
||||
<line x1="160" y1="96" x2="96" y2="160" />
|
||||
<line x1="160" y1="160" x2="96" y2="96" />
|
||||
</Icon>
|
||||
)
|
||||
95
fakemui/styles/components/Skeleton.module.scss
Normal file
95
fakemui/styles/components/Skeleton.module.scss
Normal file
@@ -0,0 +1,95 @@
|
||||
// Skeleton component styles
|
||||
// Loading placeholder with shimmer animation
|
||||
|
||||
@keyframes skeleton-shimmer {
|
||||
0% {
|
||||
background-position: -200px 0;
|
||||
}
|
||||
100% {
|
||||
background-position: calc(200px + 100%) 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fakemui-skeleton {
|
||||
display: inline-block;
|
||||
background-color: var(--color-secondary, rgba(0, 0, 0, 0.11));
|
||||
background-image: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
var(--skeleton-shimmer-color, rgba(255, 255, 255, 0.5)) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
background-size: 200px 100%;
|
||||
background-repeat: no-repeat;
|
||||
animation: skeleton-shimmer 1.5s ease-in-out infinite;
|
||||
|
||||
// Variants
|
||||
&--text {
|
||||
height: 1em;
|
||||
border-radius: var(--radius-xs, 4px);
|
||||
transform: scale(1, 0.6);
|
||||
transform-origin: 0 60%;
|
||||
|
||||
&:empty::before {
|
||||
content: '\00a0'; // Non-breaking space for height
|
||||
}
|
||||
}
|
||||
|
||||
&--rectangular {
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
}
|
||||
|
||||
&--circular {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&--rounded {
|
||||
border-radius: var(--radius-md, 8px);
|
||||
}
|
||||
|
||||
// Wave animation variant
|
||||
&--wave {
|
||||
animation: none;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
var(--skeleton-shimmer-color, rgba(255, 255, 255, 0.5)),
|
||||
transparent
|
||||
);
|
||||
animation: skeleton-shimmer 1.6s linear 0.5s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
// Pulse animation variant
|
||||
&--pulse {
|
||||
animation: skeleton-pulse 1.5s ease-in-out 0.5s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes skeleton-pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Dark theme adjustments
|
||||
[data-theme="dark"] .fakemui-skeleton {
|
||||
background-color: rgba(255, 255, 255, 0.11);
|
||||
--skeleton-shimmer-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
@@ -1,44 +1,5 @@
|
||||
-- Admin settings dialog
|
||||
-- DEPRECATED: This file redirects to settings/ directory
|
||||
-- Functions are now split into single-function files
|
||||
|
||||
---@class SettingsDialog
|
||||
local M = {}
|
||||
|
||||
---@class UIComponent
|
||||
---@field type string
|
||||
---@field props? table
|
||||
---@field children? table
|
||||
|
||||
---@return UIComponent
|
||||
function M.render_general()
|
||||
return {
|
||||
type = "dialog",
|
||||
props = {
|
||||
title = "General Settings",
|
||||
size = "large"
|
||||
},
|
||||
children = {
|
||||
{ type = "text_field", props = { label = "Site Name", name = "site_name" } },
|
||||
{ type = "text_field", props = { label = "Admin Email", name = "admin_email", type = "email" } },
|
||||
{ type = "switch", props = { label = "Maintenance Mode", name = "maintenance" } },
|
||||
{ type = "switch", props = { label = "Allow Registration", name = "allow_registration" } }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
---@return UIComponent
|
||||
function M.render_security()
|
||||
return {
|
||||
type = "dialog",
|
||||
props = {
|
||||
title = "Security Settings",
|
||||
size = "medium"
|
||||
},
|
||||
children = {
|
||||
{ type = "number_field", props = { label = "Session Timeout (min)", name = "session_timeout", min = 5 } },
|
||||
{ type = "number_field", props = { label = "Max Login Attempts", name = "max_attempts", min = 1 } },
|
||||
{ type = "switch", props = { label = "Require 2FA", name = "require_2fa" } }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
||||
return require("settings.init")
|
||||
|
||||
@@ -1,52 +1,5 @@
|
||||
-- User management dialog
|
||||
-- DEPRECATED: This file redirects to user/ directory
|
||||
-- Functions are now split into single-function files
|
||||
|
||||
---@class UserDialog
|
||||
local M = {}
|
||||
|
||||
---@class User
|
||||
---@field username string
|
||||
---@field email string
|
||||
---@field role string
|
||||
---@field active boolean
|
||||
|
||||
---@class UIComponent
|
||||
---@field type string
|
||||
---@field props? table
|
||||
---@field children? table
|
||||
|
||||
---@return UIComponent
|
||||
function M.render_create()
|
||||
return {
|
||||
type = "dialog",
|
||||
props = {
|
||||
title = "Create User",
|
||||
size = "medium"
|
||||
},
|
||||
children = {
|
||||
{ type = "text_field", props = { label = "Username", name = "username", required = true } },
|
||||
{ type = "text_field", props = { label = "Email", name = "email", type = "email", required = true } },
|
||||
{ type = "password_field", props = { label = "Password", name = "password", required = true } },
|
||||
{ type = "select", props = { label = "Role", name = "role", options = {"user", "admin"} } }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
---@param user User
|
||||
---@return UIComponent
|
||||
function M.render_edit(user)
|
||||
return {
|
||||
type = "dialog",
|
||||
props = {
|
||||
title = "Edit User",
|
||||
size = "medium"
|
||||
},
|
||||
children = {
|
||||
{ type = "text_field", props = { label = "Username", name = "username", value = user.username } },
|
||||
{ type = "text_field", props = { label = "Email", name = "email", value = user.email } },
|
||||
{ type = "select", props = { label = "Role", name = "role", value = user.role, options = {"user", "admin"} } },
|
||||
{ type = "checkbox", props = { label = "Active", name = "active", checked = user.active } }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
||||
return require("user.init")
|
||||
|
||||
16
packages/admin_dialog/seed/scripts/user/init.lua
Normal file
16
packages/admin_dialog/seed/scripts/user/init.lua
Normal file
@@ -0,0 +1,16 @@
|
||||
-- User dialog module facade
|
||||
-- Re-exports all user dialog functions for backward compatibility
|
||||
-- Each function is defined in its own file following 1-function-per-file pattern
|
||||
|
||||
---@class UserDialog
|
||||
local M = {}
|
||||
|
||||
-- Import all single-function modules
|
||||
local renderCreate = require("user.render_create")
|
||||
local renderEdit = require("user.render_edit")
|
||||
|
||||
-- Re-export all functions
|
||||
M.render_create = renderCreate.render_create
|
||||
M.render_edit = renderEdit.render_edit
|
||||
|
||||
return M
|
||||
25
packages/admin_dialog/seed/scripts/user/render_create.lua
Normal file
25
packages/admin_dialog/seed/scripts/user/render_create.lua
Normal file
@@ -0,0 +1,25 @@
|
||||
-- User create dialog
|
||||
-- Single function module for admin user dialogs
|
||||
|
||||
---@class RenderCreate
|
||||
local M = {}
|
||||
|
||||
---Render create user dialog
|
||||
---@return UIComponent
|
||||
function M.render_create()
|
||||
return {
|
||||
type = "dialog",
|
||||
props = {
|
||||
title = "Create User",
|
||||
size = "medium"
|
||||
},
|
||||
children = {
|
||||
{ type = "text_field", props = { label = "Username", name = "username", required = true } },
|
||||
{ type = "text_field", props = { label = "Email", name = "email", type = "email", required = true } },
|
||||
{ type = "password_field", props = { label = "Password", name = "password", required = true } },
|
||||
{ type = "select", props = { label = "Role", name = "role", options = {"user", "admin"} } }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
||||
26
packages/admin_dialog/seed/scripts/user/render_edit.lua
Normal file
26
packages/admin_dialog/seed/scripts/user/render_edit.lua
Normal file
@@ -0,0 +1,26 @@
|
||||
-- User edit dialog
|
||||
-- Single function module for admin user dialogs
|
||||
|
||||
---@class RenderEdit
|
||||
local M = {}
|
||||
|
||||
---Render edit user dialog
|
||||
---@param user User User data to edit
|
||||
---@return UIComponent
|
||||
function M.render_edit(user)
|
||||
return {
|
||||
type = "dialog",
|
||||
props = {
|
||||
title = "Edit User",
|
||||
size = "medium"
|
||||
},
|
||||
children = {
|
||||
{ type = "text_field", props = { label = "Username", name = "username", value = user.username } },
|
||||
{ type = "text_field", props = { label = "Email", name = "email", value = user.email } },
|
||||
{ type = "select", props = { label = "Role", name = "role", value = user.role, options = {"user", "admin"} } },
|
||||
{ type = "checkbox", props = { label = "Active", name = "active", checked = user.active } }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
||||
15
packages/admin_dialog/seed/scripts/user/types.lua
Normal file
15
packages/admin_dialog/seed/scripts/user/types.lua
Normal file
@@ -0,0 +1,15 @@
|
||||
-- Type definitions for user dialog module
|
||||
-- Shared across all user dialog functions
|
||||
|
||||
---@class User
|
||||
---@field username string
|
||||
---@field email string
|
||||
---@field role string
|
||||
---@field active boolean
|
||||
|
||||
---@class UIComponent
|
||||
---@field type string
|
||||
---@field props? table
|
||||
---@field children? table
|
||||
|
||||
return {}
|
||||
@@ -1,235 +1,5 @@
|
||||
-- Export utilities for data tables
|
||||
-- Provides CSV and JSON export functionality
|
||||
-- DEPRECATED: This file redirects to export/ directory
|
||||
-- Functions are now split into single-function files
|
||||
|
||||
---@class Export
|
||||
local M = {}
|
||||
|
||||
---@class ExportOptions
|
||||
---@field includeHeaders boolean Include column headers in export
|
||||
---@field columns string[]|nil Specific columns to export (nil = all)
|
||||
---@field delimiter string CSV delimiter (default: ",")
|
||||
---@field lineEnding string Line ending (default: "\n")
|
||||
|
||||
---Escape a value for CSV
|
||||
---@param value any Value to escape
|
||||
---@return string Escaped CSV string
|
||||
function M.escapeCsv(value)
|
||||
if value == nil then
|
||||
return ""
|
||||
end
|
||||
|
||||
local str = tostring(value)
|
||||
|
||||
-- Check if escaping is needed
|
||||
if string.find(str, '[,"\r\n]') then
|
||||
-- Escape double quotes by doubling them
|
||||
str = string.gsub(str, '"', '""')
|
||||
-- Wrap in double quotes
|
||||
str = '"' .. str .. '"'
|
||||
end
|
||||
|
||||
return str
|
||||
end
|
||||
|
||||
---Get column values from row data
|
||||
---@param row table Row data object
|
||||
---@param columns table[] Column definitions
|
||||
---@param columnIds string[]|nil Specific column IDs (nil = all)
|
||||
---@return string[] Array of values
|
||||
function M.getRowValues(row, columns, columnIds)
|
||||
local values = {}
|
||||
|
||||
if columnIds then
|
||||
-- Specific columns only
|
||||
for _, id in ipairs(columnIds) do
|
||||
table.insert(values, row[id])
|
||||
end
|
||||
else
|
||||
-- All columns
|
||||
for _, col in ipairs(columns) do
|
||||
if col.type ~= "actions" then
|
||||
table.insert(values, row[col.id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return values
|
||||
end
|
||||
|
||||
---Get column labels for headers
|
||||
---@param columns table[] Column definitions
|
||||
---@param columnIds string[]|nil Specific column IDs (nil = all)
|
||||
---@return string[] Array of labels
|
||||
function M.getColumnLabels(columns, columnIds)
|
||||
local labels = {}
|
||||
|
||||
if columnIds then
|
||||
-- Specific columns only
|
||||
for _, id in ipairs(columnIds) do
|
||||
for _, col in ipairs(columns) do
|
||||
if col.id == id then
|
||||
table.insert(labels, col.label or col.id)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
-- All columns (except actions)
|
||||
for _, col in ipairs(columns) do
|
||||
if col.type ~= "actions" then
|
||||
table.insert(labels, col.label or col.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return labels
|
||||
end
|
||||
|
||||
---Export data to CSV format
|
||||
---@param data table[] Array of row data objects
|
||||
---@param columns table[] Column definitions
|
||||
---@param options? ExportOptions Export options
|
||||
---@return string CSV formatted string
|
||||
function M.exportToCsv(data, columns, options)
|
||||
options = options or {}
|
||||
local includeHeaders = options.includeHeaders ~= false
|
||||
local columnIds = options.columns
|
||||
local delimiter = options.delimiter or ","
|
||||
local lineEnding = options.lineEnding or "\n"
|
||||
|
||||
local lines = {}
|
||||
|
||||
-- Add header row
|
||||
if includeHeaders then
|
||||
local headers = M.getColumnLabels(columns, columnIds)
|
||||
local escapedHeaders = {}
|
||||
for _, h in ipairs(headers) do
|
||||
table.insert(escapedHeaders, M.escapeCsv(h))
|
||||
end
|
||||
table.insert(lines, table.concat(escapedHeaders, delimiter))
|
||||
end
|
||||
|
||||
-- Add data rows
|
||||
for _, row in ipairs(data) do
|
||||
local values = M.getRowValues(row, columns, columnIds)
|
||||
local escapedValues = {}
|
||||
for _, v in ipairs(values) do
|
||||
table.insert(escapedValues, M.escapeCsv(v))
|
||||
end
|
||||
table.insert(lines, table.concat(escapedValues, delimiter))
|
||||
end
|
||||
|
||||
return table.concat(lines, lineEnding)
|
||||
end
|
||||
|
||||
---Serialize a value to JSON
|
||||
---@param value any Value to serialize
|
||||
---@return string JSON string
|
||||
function M.jsonEncode(value)
|
||||
local t = type(value)
|
||||
|
||||
if value == nil then
|
||||
return "null"
|
||||
elseif t == "boolean" then
|
||||
return value and "true" or "false"
|
||||
elseif t == "number" then
|
||||
return tostring(value)
|
||||
elseif t == "string" then
|
||||
-- Escape special characters
|
||||
local escaped = value
|
||||
escaped = string.gsub(escaped, '\\', '\\\\')
|
||||
escaped = string.gsub(escaped, '"', '\\"')
|
||||
escaped = string.gsub(escaped, '\n', '\\n')
|
||||
escaped = string.gsub(escaped, '\r', '\\r')
|
||||
escaped = string.gsub(escaped, '\t', '\\t')
|
||||
return '"' .. escaped .. '"'
|
||||
elseif t == "table" then
|
||||
-- Check if array or object
|
||||
local isArray = true
|
||||
local maxIndex = 0
|
||||
for k, _ in pairs(value) do
|
||||
if type(k) ~= "number" or k < 1 or k ~= math.floor(k) then
|
||||
isArray = false
|
||||
break
|
||||
end
|
||||
if k > maxIndex then maxIndex = k end
|
||||
end
|
||||
isArray = isArray and maxIndex == #value
|
||||
|
||||
if isArray then
|
||||
local items = {}
|
||||
for _, v in ipairs(value) do
|
||||
table.insert(items, M.jsonEncode(v))
|
||||
end
|
||||
return "[" .. table.concat(items, ",") .. "]"
|
||||
else
|
||||
local items = {}
|
||||
for k, v in pairs(value) do
|
||||
table.insert(items, M.jsonEncode(tostring(k)) .. ":" .. M.jsonEncode(v))
|
||||
end
|
||||
return "{" .. table.concat(items, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
return "null"
|
||||
end
|
||||
|
||||
---Export data to JSON format
|
||||
---@param data table[] Array of row data objects
|
||||
---@param columns? table[] Column definitions (optional, for column filtering)
|
||||
---@param columnIds? string[] Specific columns to export (nil = all)
|
||||
---@return string JSON formatted string
|
||||
function M.exportToJson(data, columns, columnIds)
|
||||
local result
|
||||
|
||||
if columns and columnIds then
|
||||
-- Export only specified columns
|
||||
result = {}
|
||||
for _, row in ipairs(data) do
|
||||
local filtered = {}
|
||||
for _, id in ipairs(columnIds) do
|
||||
filtered[id] = row[id]
|
||||
end
|
||||
table.insert(result, filtered)
|
||||
end
|
||||
else
|
||||
result = data
|
||||
end
|
||||
|
||||
return M.jsonEncode(result)
|
||||
end
|
||||
|
||||
---Create a download-ready export object
|
||||
---@param content string Export content
|
||||
---@param filename string Suggested filename
|
||||
---@param mimeType string MIME type
|
||||
---@return table Export object with content, filename, mimeType
|
||||
function M.createExport(content, filename, mimeType)
|
||||
return {
|
||||
content = content,
|
||||
filename = filename,
|
||||
mimeType = mimeType
|
||||
}
|
||||
end
|
||||
|
||||
---Export to CSV with download metadata
|
||||
---@param data table[] Array of row data objects
|
||||
---@param columns table[] Column definitions
|
||||
---@param filename? string Suggested filename (default: "export.csv")
|
||||
---@param options? ExportOptions Export options
|
||||
---@return table Export object
|
||||
function M.downloadCsv(data, columns, filename, options)
|
||||
local csv = M.exportToCsv(data, columns, options)
|
||||
return M.createExport(csv, filename or "export.csv", "text/csv")
|
||||
end
|
||||
|
||||
---Export to JSON with download metadata
|
||||
---@param data table[] Array of row data objects
|
||||
---@param filename? string Suggested filename (default: "export.json")
|
||||
---@return table Export object
|
||||
function M.downloadJson(data, filename)
|
||||
local json = M.exportToJson(data)
|
||||
return M.createExport(json, filename or "export.json", "application/json")
|
||||
end
|
||||
|
||||
return M
|
||||
return require("export.init")
|
||||
|
||||
20
packages/data_table/seed/scripts/export/create_export.lua
Normal file
20
packages/data_table/seed/scripts/export/create_export.lua
Normal file
@@ -0,0 +1,20 @@
|
||||
-- Create a download-ready export object
|
||||
-- Single function module for data table export
|
||||
|
||||
---@class CreateExport
|
||||
local M = {}
|
||||
|
||||
---Create a download-ready export object
|
||||
---@param content string Export content
|
||||
---@param filename string Suggested filename
|
||||
---@param mimeType string MIME type
|
||||
---@return ExportObject Export object with content, filename, mimeType
|
||||
function M.createExport(content, filename, mimeType)
|
||||
return {
|
||||
content = content,
|
||||
filename = filename,
|
||||
mimeType = mimeType
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
||||
21
packages/data_table/seed/scripts/export/download_csv.lua
Normal file
21
packages/data_table/seed/scripts/export/download_csv.lua
Normal file
@@ -0,0 +1,21 @@
|
||||
-- Export to CSV with download metadata
|
||||
-- Single function module for data table export
|
||||
|
||||
local exportToCsv = require("export.export_to_csv")
|
||||
local createExport = require("export.create_export")
|
||||
|
||||
---@class DownloadCsv
|
||||
local M = {}
|
||||
|
||||
---Export to CSV with download metadata
|
||||
---@param data table[] Array of row data objects
|
||||
---@param columns table[] Column definitions
|
||||
---@param filename? string Suggested filename (default: "export.csv")
|
||||
---@param options? ExportOptions Export options
|
||||
---@return ExportObject Export object
|
||||
function M.downloadCsv(data, columns, filename, options)
|
||||
local csv = exportToCsv.exportToCsv(data, columns, options)
|
||||
return createExport.createExport(csv, filename or "export.csv", "text/csv")
|
||||
end
|
||||
|
||||
return M
|
||||
19
packages/data_table/seed/scripts/export/download_json.lua
Normal file
19
packages/data_table/seed/scripts/export/download_json.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
-- Export to JSON with download metadata
|
||||
-- Single function module for data table export
|
||||
|
||||
local exportToJson = require("export.export_to_json")
|
||||
local createExport = require("export.create_export")
|
||||
|
||||
---@class DownloadJson
|
||||
local M = {}
|
||||
|
||||
---Export to JSON with download metadata
|
||||
---@param data table[] Array of row data objects
|
||||
---@param filename? string Suggested filename (default: "export.json")
|
||||
---@return ExportObject Export object
|
||||
function M.downloadJson(data, filename)
|
||||
local json = exportToJson.exportToJson(data)
|
||||
return createExport.createExport(json, filename or "export.json", "application/json")
|
||||
end
|
||||
|
||||
return M
|
||||
28
packages/data_table/seed/scripts/export/escape_csv.lua
Normal file
28
packages/data_table/seed/scripts/export/escape_csv.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
-- Escape a value for CSV
|
||||
-- Single function module for data table export
|
||||
|
||||
---@class EscapeCsv
|
||||
local M = {}
|
||||
|
||||
---Escape a value for CSV
|
||||
---@param value any Value to escape
|
||||
---@return string Escaped CSV string
|
||||
function M.escapeCsv(value)
|
||||
if value == nil then
|
||||
return ""
|
||||
end
|
||||
|
||||
local str = tostring(value)
|
||||
|
||||
-- Check if escaping is needed
|
||||
if string.find(str, '[,"\r\n]') then
|
||||
-- Escape double quotes by doubling them
|
||||
str = string.gsub(str, '"', '""')
|
||||
-- Wrap in double quotes
|
||||
str = '"' .. str .. '"'
|
||||
end
|
||||
|
||||
return str
|
||||
end
|
||||
|
||||
return M
|
||||
48
packages/data_table/seed/scripts/export/export_to_csv.lua
Normal file
48
packages/data_table/seed/scripts/export/export_to_csv.lua
Normal file
@@ -0,0 +1,48 @@
|
||||
-- Export data to CSV format
|
||||
-- Single function module for data table export
|
||||
|
||||
local escapeCsv = require("export.escape_csv")
|
||||
local getColumnLabels = require("export.get_column_labels")
|
||||
local getRowValues = require("export.get_row_values")
|
||||
|
||||
---@class ExportToCsv
|
||||
local M = {}
|
||||
|
||||
---Export data to CSV format
|
||||
---@param data table[] Array of row data objects
|
||||
---@param columns table[] Column definitions
|
||||
---@param options? ExportOptions Export options
|
||||
---@return string CSV formatted string
|
||||
function M.exportToCsv(data, columns, options)
|
||||
options = options or {}
|
||||
local includeHeaders = options.includeHeaders ~= false
|
||||
local columnIds = options.columns
|
||||
local delimiter = options.delimiter or ","
|
||||
local lineEnding = options.lineEnding or "\n"
|
||||
|
||||
local lines = {}
|
||||
|
||||
-- Add header row
|
||||
if includeHeaders then
|
||||
local headers = getColumnLabels.getColumnLabels(columns, columnIds)
|
||||
local escapedHeaders = {}
|
||||
for _, h in ipairs(headers) do
|
||||
table.insert(escapedHeaders, escapeCsv.escapeCsv(h))
|
||||
end
|
||||
table.insert(lines, table.concat(escapedHeaders, delimiter))
|
||||
end
|
||||
|
||||
-- Add data rows
|
||||
for _, row in ipairs(data) do
|
||||
local values = getRowValues.getRowValues(row, columns, columnIds)
|
||||
local escapedValues = {}
|
||||
for _, v in ipairs(values) do
|
||||
table.insert(escapedValues, escapeCsv.escapeCsv(v))
|
||||
end
|
||||
table.insert(lines, table.concat(escapedValues, delimiter))
|
||||
end
|
||||
|
||||
return table.concat(lines, lineEnding)
|
||||
end
|
||||
|
||||
return M
|
||||
34
packages/data_table/seed/scripts/export/export_to_json.lua
Normal file
34
packages/data_table/seed/scripts/export/export_to_json.lua
Normal file
@@ -0,0 +1,34 @@
|
||||
-- Export data to JSON format
|
||||
-- Single function module for data table export
|
||||
|
||||
local jsonEncode = require("export.json_encode")
|
||||
|
||||
---@class ExportToJson
|
||||
local M = {}
|
||||
|
||||
---Export data to JSON format
|
||||
---@param data table[] Array of row data objects
|
||||
---@param columns? table[] Column definitions (optional, for column filtering)
|
||||
---@param columnIds? string[] Specific columns to export (nil = all)
|
||||
---@return string JSON formatted string
|
||||
function M.exportToJson(data, columns, columnIds)
|
||||
local result
|
||||
|
||||
if columns and columnIds then
|
||||
-- Export only specified columns
|
||||
result = {}
|
||||
for _, row in ipairs(data) do
|
||||
local filtered = {}
|
||||
for _, id in ipairs(columnIds) do
|
||||
filtered[id] = row[id]
|
||||
end
|
||||
table.insert(result, filtered)
|
||||
end
|
||||
else
|
||||
result = data
|
||||
end
|
||||
|
||||
return jsonEncode.jsonEncode(result)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,36 @@
|
||||
-- Get column labels for headers
|
||||
-- Single function module for data table export
|
||||
|
||||
---@class GetColumnLabels
|
||||
local M = {}
|
||||
|
||||
---Get column labels for headers
|
||||
---@param columns table[] Column definitions
|
||||
---@param columnIds string[]|nil Specific column IDs (nil = all)
|
||||
---@return string[] Array of labels
|
||||
function M.getColumnLabels(columns, columnIds)
|
||||
local labels = {}
|
||||
|
||||
if columnIds then
|
||||
-- Specific columns only
|
||||
for _, id in ipairs(columnIds) do
|
||||
for _, col in ipairs(columns) do
|
||||
if col.id == id then
|
||||
table.insert(labels, col.label or col.id)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
-- All columns (except actions)
|
||||
for _, col in ipairs(columns) do
|
||||
if col.type ~= "actions" then
|
||||
table.insert(labels, col.label or col.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return labels
|
||||
end
|
||||
|
||||
return M
|
||||
32
packages/data_table/seed/scripts/export/get_row_values.lua
Normal file
32
packages/data_table/seed/scripts/export/get_row_values.lua
Normal file
@@ -0,0 +1,32 @@
|
||||
-- Get column values from row data
|
||||
-- Single function module for data table export
|
||||
|
||||
---@class GetRowValues
|
||||
local M = {}
|
||||
|
||||
---Get column values from row data
|
||||
---@param row table Row data object
|
||||
---@param columns table[] Column definitions
|
||||
---@param columnIds string[]|nil Specific column IDs (nil = all)
|
||||
---@return string[] Array of values
|
||||
function M.getRowValues(row, columns, columnIds)
|
||||
local values = {}
|
||||
|
||||
if columnIds then
|
||||
-- Specific columns only
|
||||
for _, id in ipairs(columnIds) do
|
||||
table.insert(values, row[id])
|
||||
end
|
||||
else
|
||||
-- All columns
|
||||
for _, col in ipairs(columns) do
|
||||
if col.type ~= "actions" then
|
||||
table.insert(values, row[col.id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return values
|
||||
end
|
||||
|
||||
return M
|
||||
30
packages/data_table/seed/scripts/export/init.lua
Normal file
30
packages/data_table/seed/scripts/export/init.lua
Normal file
@@ -0,0 +1,30 @@
|
||||
-- Export module facade
|
||||
-- Re-exports all export functions for backward compatibility
|
||||
-- Each function is defined in its own file following 1-function-per-file pattern
|
||||
|
||||
---@class Export
|
||||
local M = {}
|
||||
|
||||
-- Import all single-function modules
|
||||
local escapeCsv = require("export.escape_csv")
|
||||
local getRowValues = require("export.get_row_values")
|
||||
local getColumnLabels = require("export.get_column_labels")
|
||||
local exportToCsv = require("export.export_to_csv")
|
||||
local jsonEncode = require("export.json_encode")
|
||||
local exportToJson = require("export.export_to_json")
|
||||
local createExport = require("export.create_export")
|
||||
local downloadCsv = require("export.download_csv")
|
||||
local downloadJson = require("export.download_json")
|
||||
|
||||
-- Re-export all functions
|
||||
M.escapeCsv = escapeCsv.escapeCsv
|
||||
M.getRowValues = getRowValues.getRowValues
|
||||
M.getColumnLabels = getColumnLabels.getColumnLabels
|
||||
M.exportToCsv = exportToCsv.exportToCsv
|
||||
M.jsonEncode = jsonEncode.jsonEncode
|
||||
M.exportToJson = exportToJson.exportToJson
|
||||
M.createExport = createExport.createExport
|
||||
M.downloadCsv = downloadCsv.downloadCsv
|
||||
M.downloadJson = downloadJson.downloadJson
|
||||
|
||||
return M
|
||||
59
packages/data_table/seed/scripts/export/json_encode.lua
Normal file
59
packages/data_table/seed/scripts/export/json_encode.lua
Normal file
@@ -0,0 +1,59 @@
|
||||
-- Serialize a value to JSON
|
||||
-- Single function module for data table export
|
||||
|
||||
---@class JsonEncode
|
||||
local M = {}
|
||||
|
||||
---Serialize a value to JSON
|
||||
---@param value any Value to serialize
|
||||
---@return string JSON string
|
||||
function M.jsonEncode(value)
|
||||
local t = type(value)
|
||||
|
||||
if value == nil then
|
||||
return "null"
|
||||
elseif t == "boolean" then
|
||||
return value and "true" or "false"
|
||||
elseif t == "number" then
|
||||
return tostring(value)
|
||||
elseif t == "string" then
|
||||
-- Escape special characters
|
||||
local escaped = value
|
||||
escaped = string.gsub(escaped, '\\', '\\\\')
|
||||
escaped = string.gsub(escaped, '"', '\\"')
|
||||
escaped = string.gsub(escaped, '\n', '\\n')
|
||||
escaped = string.gsub(escaped, '\r', '\\r')
|
||||
escaped = string.gsub(escaped, '\t', '\\t')
|
||||
return '"' .. escaped .. '"'
|
||||
elseif t == "table" then
|
||||
-- Check if array or object
|
||||
local isArray = true
|
||||
local maxIndex = 0
|
||||
for k, _ in pairs(value) do
|
||||
if type(k) ~= "number" or k < 1 or k ~= math.floor(k) then
|
||||
isArray = false
|
||||
break
|
||||
end
|
||||
if k > maxIndex then maxIndex = k end
|
||||
end
|
||||
isArray = isArray and maxIndex == #value
|
||||
|
||||
if isArray then
|
||||
local items = {}
|
||||
for _, v in ipairs(value) do
|
||||
table.insert(items, M.jsonEncode(v))
|
||||
end
|
||||
return "[" .. table.concat(items, ",") .. "]"
|
||||
else
|
||||
local items = {}
|
||||
for k, v in pairs(value) do
|
||||
table.insert(items, M.jsonEncode(tostring(k)) .. ":" .. M.jsonEncode(v))
|
||||
end
|
||||
return "{" .. table.concat(items, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
return "null"
|
||||
end
|
||||
|
||||
return M
|
||||
15
packages/data_table/seed/scripts/export/types.lua
Normal file
15
packages/data_table/seed/scripts/export/types.lua
Normal file
@@ -0,0 +1,15 @@
|
||||
-- Type definitions for export module
|
||||
-- Shared across all export functions
|
||||
|
||||
---@class ExportOptions
|
||||
---@field includeHeaders boolean Include column headers in export
|
||||
---@field columns string[]|nil Specific columns to export (nil = all)
|
||||
---@field delimiter string CSV delimiter (default: ",")
|
||||
---@field lineEnding string Line ending (default: "\n")
|
||||
|
||||
---@class ExportObject
|
||||
---@field content string Export content
|
||||
---@field filename string Suggested filename
|
||||
---@field mimeType string MIME type
|
||||
|
||||
return {}
|
||||
@@ -1,159 +1,5 @@
|
||||
-- Filtering utilities for data tables
|
||||
-- Provides filter application and state management
|
||||
-- DEPRECATED: This file redirects to filtering/ directory
|
||||
-- Functions are now split into single-function files
|
||||
|
||||
---@class Filtering
|
||||
local M = {}
|
||||
|
||||
---@alias FilterOperator "equals" | "contains" | "startsWith" | "endsWith" | "gt" | "lt" | "gte" | "lte" | "between"
|
||||
|
||||
---@class Filter
|
||||
---@field column_id string Column identifier to filter
|
||||
---@field operator FilterOperator Filter operator
|
||||
---@field value any Filter value (single value or {min, max} for between)
|
||||
|
||||
---@class FilterState
|
||||
---@field filters Filter[] Active filters
|
||||
|
||||
---Create initial filter state
|
||||
---@return FilterState
|
||||
function M.createFilterState()
|
||||
return {
|
||||
filters = {}
|
||||
}
|
||||
end
|
||||
|
||||
---Check if a value matches a filter
|
||||
---@param value any Value to check
|
||||
---@param filter Filter Filter to apply
|
||||
---@return boolean Whether value matches filter
|
||||
function M.matchesFilter(value, filter)
|
||||
local op = filter.operator
|
||||
local filterVal = filter.value
|
||||
|
||||
-- Handle nil values
|
||||
if value == nil then
|
||||
return op == "equals" and filterVal == nil
|
||||
end
|
||||
|
||||
if op == "equals" then
|
||||
return value == filterVal
|
||||
elseif op == "contains" then
|
||||
return string.find(string.lower(tostring(value)), string.lower(tostring(filterVal)), 1, true) ~= nil
|
||||
elseif op == "startsWith" then
|
||||
local str = string.lower(tostring(value))
|
||||
local prefix = string.lower(tostring(filterVal))
|
||||
return string.sub(str, 1, #prefix) == prefix
|
||||
elseif op == "endsWith" then
|
||||
local str = string.lower(tostring(value))
|
||||
local suffix = string.lower(tostring(filterVal))
|
||||
return string.sub(str, -#suffix) == suffix
|
||||
elseif op == "gt" then
|
||||
return tonumber(value) and tonumber(value) > tonumber(filterVal)
|
||||
elseif op == "lt" then
|
||||
return tonumber(value) and tonumber(value) < tonumber(filterVal)
|
||||
elseif op == "gte" then
|
||||
return tonumber(value) and tonumber(value) >= tonumber(filterVal)
|
||||
elseif op == "lte" then
|
||||
return tonumber(value) and tonumber(value) <= tonumber(filterVal)
|
||||
elseif op == "between" then
|
||||
local num = tonumber(value)
|
||||
return num and num >= filterVal[1] and num <= filterVal[2]
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
---Check if a row matches all filters
|
||||
---@param row table Row data object
|
||||
---@param filters Filter[] Array of filters to apply
|
||||
---@return boolean Whether row matches all filters
|
||||
function M.matchesAllFilters(row, filters)
|
||||
for _, filter in ipairs(filters) do
|
||||
if not M.matchesFilter(row[filter.column_id], filter) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
---Apply filters to data
|
||||
---@param data table[] Array of row data objects
|
||||
---@param state FilterState Filter state with active filters
|
||||
---@return table[] Filtered data array (new array, original unchanged)
|
||||
function M.applyFilters(data, state)
|
||||
if not state.filters or #state.filters == 0 then
|
||||
return data
|
||||
end
|
||||
|
||||
local filtered = {}
|
||||
for _, row in ipairs(data) do
|
||||
if M.matchesAllFilters(row, state.filters) then
|
||||
table.insert(filtered, row)
|
||||
end
|
||||
end
|
||||
|
||||
return filtered
|
||||
end
|
||||
|
||||
---Add a filter to state
|
||||
---@param state FilterState Current filter state
|
||||
---@param column_id string Column identifier
|
||||
---@param operator FilterOperator Filter operator
|
||||
---@param value any Filter value
|
||||
---@return FilterState New filter state
|
||||
function M.addFilter(state, column_id, operator, value)
|
||||
local newFilters = {}
|
||||
|
||||
-- Copy existing filters (replacing any for same column)
|
||||
for _, filter in ipairs(state.filters) do
|
||||
if filter.column_id ~= column_id then
|
||||
table.insert(newFilters, filter)
|
||||
end
|
||||
end
|
||||
|
||||
-- Add new filter
|
||||
table.insert(newFilters, {
|
||||
column_id = column_id,
|
||||
operator = operator,
|
||||
value = value
|
||||
})
|
||||
|
||||
return { filters = newFilters }
|
||||
end
|
||||
|
||||
---Remove a filter from state
|
||||
---@param state FilterState Current filter state
|
||||
---@param column_id string Column identifier to remove
|
||||
---@return FilterState New filter state
|
||||
function M.removeFilter(state, column_id)
|
||||
local newFilters = {}
|
||||
|
||||
for _, filter in ipairs(state.filters) do
|
||||
if filter.column_id ~= column_id then
|
||||
table.insert(newFilters, filter)
|
||||
end
|
||||
end
|
||||
|
||||
return { filters = newFilters }
|
||||
end
|
||||
|
||||
---Clear all filters
|
||||
---@return FilterState Empty filter state
|
||||
function M.clearFilters()
|
||||
return { filters = {} }
|
||||
end
|
||||
|
||||
---Get active filter for a column
|
||||
---@param state FilterState Current filter state
|
||||
---@param column_id string Column identifier
|
||||
---@return Filter|nil Active filter for column, or nil
|
||||
function M.getFilterForColumn(state, column_id)
|
||||
for _, filter in ipairs(state.filters) do
|
||||
if filter.column_id == column_id then
|
||||
return filter
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
return M
|
||||
return require("filtering.init")
|
||||
|
||||
33
packages/data_table/seed/scripts/filtering/add_filter.lua
Normal file
33
packages/data_table/seed/scripts/filtering/add_filter.lua
Normal file
@@ -0,0 +1,33 @@
|
||||
-- Add a filter to state
|
||||
-- Single function module for data table filtering
|
||||
|
||||
---@class AddFilter
|
||||
local M = {}
|
||||
|
||||
---Add a filter to state
|
||||
---@param state FilterState Current filter state
|
||||
---@param column_id string Column identifier
|
||||
---@param operator FilterOperator Filter operator
|
||||
---@param value any Filter value
|
||||
---@return FilterState New filter state
|
||||
function M.addFilter(state, column_id, operator, value)
|
||||
local newFilters = {}
|
||||
|
||||
-- Copy existing filters (replacing any for same column)
|
||||
for _, filter in ipairs(state.filters) do
|
||||
if filter.column_id ~= column_id then
|
||||
table.insert(newFilters, filter)
|
||||
end
|
||||
end
|
||||
|
||||
-- Add new filter
|
||||
table.insert(newFilters, {
|
||||
column_id = column_id,
|
||||
operator = operator,
|
||||
value = value
|
||||
})
|
||||
|
||||
return { filters = newFilters }
|
||||
end
|
||||
|
||||
return M
|
||||
28
packages/data_table/seed/scripts/filtering/apply_filters.lua
Normal file
28
packages/data_table/seed/scripts/filtering/apply_filters.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
-- Apply filters to data
|
||||
-- Single function module for data table filtering
|
||||
|
||||
local matchesAllFilters = require("filtering.matches_all_filters")
|
||||
|
||||
---@class ApplyFilters
|
||||
local M = {}
|
||||
|
||||
---Apply filters to data
|
||||
---@param data table[] Array of row data objects
|
||||
---@param state FilterState Filter state with active filters
|
||||
---@return table[] Filtered data array (new array, original unchanged)
|
||||
function M.applyFilters(data, state)
|
||||
if not state.filters or #state.filters == 0 then
|
||||
return data
|
||||
end
|
||||
|
||||
local filtered = {}
|
||||
for _, row in ipairs(data) do
|
||||
if matchesAllFilters.matchesAllFilters(row, state.filters) then
|
||||
table.insert(filtered, row)
|
||||
end
|
||||
end
|
||||
|
||||
return filtered
|
||||
end
|
||||
|
||||
return M
|
||||
13
packages/data_table/seed/scripts/filtering/clear_filters.lua
Normal file
13
packages/data_table/seed/scripts/filtering/clear_filters.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
-- Clear all filters
|
||||
-- Single function module for data table filtering
|
||||
|
||||
---@class ClearFilters
|
||||
local M = {}
|
||||
|
||||
---Clear all filters
|
||||
---@return FilterState Empty filter state
|
||||
function M.clearFilters()
|
||||
return { filters = {} }
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Create initial filter state
|
||||
-- Single function module for data table filtering
|
||||
|
||||
---@class CreateFilterState
|
||||
local M = {}
|
||||
|
||||
---Create initial filter state
|
||||
---@return FilterState
|
||||
function M.createFilterState()
|
||||
return {
|
||||
filters = {}
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,20 @@
|
||||
-- Get active filter for a column
|
||||
-- Single function module for data table filtering
|
||||
|
||||
---@class GetFilterForColumn
|
||||
local M = {}
|
||||
|
||||
---Get active filter for a column
|
||||
---@param state FilterState Current filter state
|
||||
---@param column_id string Column identifier
|
||||
---@return Filter|nil Active filter for column, or nil
|
||||
function M.getFilterForColumn(state, column_id)
|
||||
for _, filter in ipairs(state.filters) do
|
||||
if filter.column_id == column_id then
|
||||
return filter
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
return M
|
||||
28
packages/data_table/seed/scripts/filtering/init.lua
Normal file
28
packages/data_table/seed/scripts/filtering/init.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
-- Filtering module facade
|
||||
-- Re-exports all filtering functions for backward compatibility
|
||||
-- Each function is defined in its own file following 1-function-per-file pattern
|
||||
|
||||
---@class Filtering
|
||||
local M = {}
|
||||
|
||||
-- Import all single-function modules
|
||||
local createFilterState = require("filtering.create_filter_state")
|
||||
local matchesFilter = require("filtering.matches_filter")
|
||||
local matchesAllFilters = require("filtering.matches_all_filters")
|
||||
local applyFilters = require("filtering.apply_filters")
|
||||
local addFilter = require("filtering.add_filter")
|
||||
local removeFilter = require("filtering.remove_filter")
|
||||
local clearFilters = require("filtering.clear_filters")
|
||||
local getFilterForColumn = require("filtering.get_filter_for_column")
|
||||
|
||||
-- Re-export all functions
|
||||
M.createFilterState = createFilterState.createFilterState
|
||||
M.matchesFilter = matchesFilter.matchesFilter
|
||||
M.matchesAllFilters = matchesAllFilters.matchesAllFilters
|
||||
M.applyFilters = applyFilters.applyFilters
|
||||
M.addFilter = addFilter.addFilter
|
||||
M.removeFilter = removeFilter.removeFilter
|
||||
M.clearFilters = clearFilters.clearFilters
|
||||
M.getFilterForColumn = getFilterForColumn.getFilterForColumn
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,22 @@
|
||||
-- Check if a row matches all filters
|
||||
-- Single function module for data table filtering
|
||||
|
||||
local matchesFilter = require("filtering.matches_filter")
|
||||
|
||||
---@class MatchesAllFilters
|
||||
local M = {}
|
||||
|
||||
---Check if a row matches all filters
|
||||
---@param row table Row data object
|
||||
---@param filters Filter[] Array of filters to apply
|
||||
---@return boolean Whether row matches all filters
|
||||
function M.matchesAllFilters(row, filters)
|
||||
for _, filter in ipairs(filters) do
|
||||
if not matchesFilter.matchesFilter(row[filter.column_id], filter) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,48 @@
|
||||
-- Check if a value matches a filter
|
||||
-- Single function module for data table filtering
|
||||
|
||||
---@class MatchesFilter
|
||||
local M = {}
|
||||
|
||||
---Check if a value matches a filter
|
||||
---@param value any Value to check
|
||||
---@param filter Filter Filter to apply
|
||||
---@return boolean Whether value matches filter
|
||||
function M.matchesFilter(value, filter)
|
||||
local op = filter.operator
|
||||
local filterVal = filter.value
|
||||
|
||||
-- Handle nil values
|
||||
if value == nil then
|
||||
return op == "equals" and filterVal == nil
|
||||
end
|
||||
|
||||
if op == "equals" then
|
||||
return value == filterVal
|
||||
elseif op == "contains" then
|
||||
return string.find(string.lower(tostring(value)), string.lower(tostring(filterVal)), 1, true) ~= nil
|
||||
elseif op == "startsWith" then
|
||||
local str = string.lower(tostring(value))
|
||||
local prefix = string.lower(tostring(filterVal))
|
||||
return string.sub(str, 1, #prefix) == prefix
|
||||
elseif op == "endsWith" then
|
||||
local str = string.lower(tostring(value))
|
||||
local suffix = string.lower(tostring(filterVal))
|
||||
return string.sub(str, -#suffix) == suffix
|
||||
elseif op == "gt" then
|
||||
return tonumber(value) and tonumber(value) > tonumber(filterVal)
|
||||
elseif op == "lt" then
|
||||
return tonumber(value) and tonumber(value) < tonumber(filterVal)
|
||||
elseif op == "gte" then
|
||||
return tonumber(value) and tonumber(value) >= tonumber(filterVal)
|
||||
elseif op == "lte" then
|
||||
return tonumber(value) and tonumber(value) <= tonumber(filterVal)
|
||||
elseif op == "between" then
|
||||
local num = tonumber(value)
|
||||
return num and num >= filterVal[1] and num <= filterVal[2]
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return M
|
||||
23
packages/data_table/seed/scripts/filtering/remove_filter.lua
Normal file
23
packages/data_table/seed/scripts/filtering/remove_filter.lua
Normal file
@@ -0,0 +1,23 @@
|
||||
-- Remove a filter from state
|
||||
-- Single function module for data table filtering
|
||||
|
||||
---@class RemoveFilter
|
||||
local M = {}
|
||||
|
||||
---Remove a filter from state
|
||||
---@param state FilterState Current filter state
|
||||
---@param column_id string Column identifier to remove
|
||||
---@return FilterState New filter state
|
||||
function M.removeFilter(state, column_id)
|
||||
local newFilters = {}
|
||||
|
||||
for _, filter in ipairs(state.filters) do
|
||||
if filter.column_id ~= column_id then
|
||||
table.insert(newFilters, filter)
|
||||
end
|
||||
end
|
||||
|
||||
return { filters = newFilters }
|
||||
end
|
||||
|
||||
return M
|
||||
14
packages/data_table/seed/scripts/filtering/types.lua
Normal file
14
packages/data_table/seed/scripts/filtering/types.lua
Normal file
@@ -0,0 +1,14 @@
|
||||
-- Type definitions for filtering module
|
||||
-- Shared across all filtering functions
|
||||
|
||||
---@alias FilterOperator "equals" | "contains" | "startsWith" | "endsWith" | "gt" | "lt" | "gte" | "lte" | "between"
|
||||
|
||||
---@class Filter
|
||||
---@field column_id string Column identifier to filter
|
||||
---@field operator FilterOperator Filter operator
|
||||
---@field value any Filter value (single value or {min, max} for between)
|
||||
|
||||
---@class FilterState
|
||||
---@field filters Filter[] Active filters
|
||||
|
||||
return {}
|
||||
@@ -1,131 +1,5 @@
|
||||
-- Sorting utilities for data tables
|
||||
-- Provides column sorting logic and state management
|
||||
-- DEPRECATED: This file redirects to sorting/ directory
|
||||
-- Functions are now split into single-function files
|
||||
|
||||
---@class Sorting
|
||||
local M = {}
|
||||
|
||||
---@alias SortDirection "asc" | "desc" | nil
|
||||
|
||||
---@class SortState
|
||||
---@field column_id string|nil Column being sorted
|
||||
---@field direction SortDirection Sort direction
|
||||
|
||||
---@class SortedResult
|
||||
---@field data table[] Sorted data array
|
||||
---@field state SortState Current sort state
|
||||
|
||||
---Create initial sort state
|
||||
---@return SortState
|
||||
function M.createSortState()
|
||||
return {
|
||||
column_id = nil,
|
||||
direction = nil
|
||||
}
|
||||
end
|
||||
|
||||
---Compare two values for sorting
|
||||
---@param a any First value
|
||||
---@param b any Second value
|
||||
---@param direction SortDirection Sort direction
|
||||
---@return boolean Whether a should come before b
|
||||
function M.compare(a, b, direction)
|
||||
local aVal = a
|
||||
local bVal = b
|
||||
|
||||
-- Handle nil values (push to end)
|
||||
if aVal == nil and bVal == nil then return false end
|
||||
if aVal == nil then return direction == "desc" end
|
||||
if bVal == nil then return direction == "asc" end
|
||||
|
||||
-- Handle different types
|
||||
local aType = type(aVal)
|
||||
local bType = type(bVal)
|
||||
|
||||
if aType ~= bType then
|
||||
aVal = tostring(aVal)
|
||||
bVal = tostring(bVal)
|
||||
end
|
||||
|
||||
-- Compare based on type
|
||||
if aType == "number" then
|
||||
if direction == "desc" then
|
||||
return aVal > bVal
|
||||
else
|
||||
return aVal < bVal
|
||||
end
|
||||
else
|
||||
-- String comparison (case-insensitive)
|
||||
local aLower = string.lower(tostring(aVal))
|
||||
local bLower = string.lower(tostring(bVal))
|
||||
if direction == "desc" then
|
||||
return aLower > bLower
|
||||
else
|
||||
return aLower < bLower
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Sort data by a column
|
||||
---@param data table[] Array of row data objects
|
||||
---@param column_id string Column identifier to sort by
|
||||
---@param direction SortDirection Sort direction ("asc" or "desc")
|
||||
---@return table[] Sorted data array (new array, original unchanged)
|
||||
function M.sortByColumn(data, column_id, direction)
|
||||
if not column_id or not direction then
|
||||
return data
|
||||
end
|
||||
|
||||
-- Create a copy to avoid mutating original
|
||||
local sorted = {}
|
||||
for i, row in ipairs(data) do
|
||||
sorted[i] = row
|
||||
end
|
||||
|
||||
table.sort(sorted, function(a, b)
|
||||
return M.compare(a[column_id], b[column_id], direction)
|
||||
end)
|
||||
|
||||
return sorted
|
||||
end
|
||||
|
||||
---Toggle sort state for a column
|
||||
---@param state SortState Current sort state
|
||||
---@param column_id string Column identifier clicked
|
||||
---@return SortState New sort state
|
||||
function M.toggleSort(state, column_id)
|
||||
if state.column_id ~= column_id then
|
||||
-- New column: start with ascending
|
||||
return {
|
||||
column_id = column_id,
|
||||
direction = "asc"
|
||||
}
|
||||
elseif state.direction == "asc" then
|
||||
-- Same column, was ascending: switch to descending
|
||||
return {
|
||||
column_id = column_id,
|
||||
direction = "desc"
|
||||
}
|
||||
else
|
||||
-- Same column, was descending: clear sort
|
||||
return {
|
||||
column_id = nil,
|
||||
direction = nil
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
---Get sort indicator for a column
|
||||
---@param state SortState Current sort state
|
||||
---@param column_id string Column identifier to check
|
||||
---@return string|nil Sort indicator ("▲", "▼", or nil)
|
||||
function M.getSortIndicator(state, column_id)
|
||||
if state.column_id ~= column_id then
|
||||
return nil
|
||||
elseif state.direction == "asc" then
|
||||
return "▲"
|
||||
else
|
||||
return "▼"
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
return require("sorting.init")
|
||||
|
||||
49
packages/data_table/seed/scripts/sorting/compare.lua
Normal file
49
packages/data_table/seed/scripts/sorting/compare.lua
Normal file
@@ -0,0 +1,49 @@
|
||||
-- Compare two values for sorting
|
||||
-- Single function module for data table sorting
|
||||
|
||||
---@class Compare
|
||||
local M = {}
|
||||
|
||||
---Compare two values for sorting
|
||||
---@param a any First value
|
||||
---@param b any Second value
|
||||
---@param direction SortDirection Sort direction
|
||||
---@return boolean Whether a should come before b
|
||||
function M.compare(a, b, direction)
|
||||
local aVal = a
|
||||
local bVal = b
|
||||
|
||||
-- Handle nil values (push to end)
|
||||
if aVal == nil and bVal == nil then return false end
|
||||
if aVal == nil then return direction == "desc" end
|
||||
if bVal == nil then return direction == "asc" end
|
||||
|
||||
-- Handle different types
|
||||
local aType = type(aVal)
|
||||
local bType = type(bVal)
|
||||
|
||||
if aType ~= bType then
|
||||
aVal = tostring(aVal)
|
||||
bVal = tostring(bVal)
|
||||
end
|
||||
|
||||
-- Compare based on type
|
||||
if aType == "number" then
|
||||
if direction == "desc" then
|
||||
return aVal > bVal
|
||||
else
|
||||
return aVal < bVal
|
||||
end
|
||||
else
|
||||
-- String comparison (case-insensitive)
|
||||
local aLower = string.lower(tostring(aVal))
|
||||
local bLower = string.lower(tostring(bVal))
|
||||
if direction == "desc" then
|
||||
return aLower > bLower
|
||||
else
|
||||
return aLower < bLower
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,16 @@
|
||||
-- Create initial sort state
|
||||
-- Single function module for data table sorting
|
||||
|
||||
---@class CreateSortState
|
||||
local M = {}
|
||||
|
||||
---Create initial sort state
|
||||
---@return SortState
|
||||
function M.createSortState()
|
||||
return {
|
||||
column_id = nil,
|
||||
direction = nil
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,21 @@
|
||||
-- Get sort indicator for a column
|
||||
-- Single function module for data table sorting
|
||||
|
||||
---@class GetSortIndicator
|
||||
local M = {}
|
||||
|
||||
---Get sort indicator for a column
|
||||
---@param state SortState Current sort state
|
||||
---@param column_id string Column identifier to check
|
||||
---@return string|nil Sort indicator ("▲", "▼", or nil)
|
||||
function M.getSortIndicator(state, column_id)
|
||||
if state.column_id ~= column_id then
|
||||
return nil
|
||||
elseif state.direction == "asc" then
|
||||
return "▲"
|
||||
else
|
||||
return "▼"
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
22
packages/data_table/seed/scripts/sorting/init.lua
Normal file
22
packages/data_table/seed/scripts/sorting/init.lua
Normal file
@@ -0,0 +1,22 @@
|
||||
-- Sorting module facade
|
||||
-- Re-exports all sorting functions for backward compatibility
|
||||
-- Each function is defined in its own file following 1-function-per-file pattern
|
||||
|
||||
---@class Sorting
|
||||
local M = {}
|
||||
|
||||
-- Import all single-function modules
|
||||
local createSortState = require("sorting.create_sort_state")
|
||||
local compare = require("sorting.compare")
|
||||
local sortByColumn = require("sorting.sort_by_column")
|
||||
local toggleSort = require("sorting.toggle_sort")
|
||||
local getSortIndicator = require("sorting.get_sort_indicator")
|
||||
|
||||
-- Re-export all functions
|
||||
M.createSortState = createSortState.createSortState
|
||||
M.compare = compare.compare
|
||||
M.sortByColumn = sortByColumn.sortByColumn
|
||||
M.toggleSort = toggleSort.toggleSort
|
||||
M.getSortIndicator = getSortIndicator.getSortIndicator
|
||||
|
||||
return M
|
||||
32
packages/data_table/seed/scripts/sorting/sort_by_column.lua
Normal file
32
packages/data_table/seed/scripts/sorting/sort_by_column.lua
Normal file
@@ -0,0 +1,32 @@
|
||||
-- Sort data by a column
|
||||
-- Single function module for data table sorting
|
||||
|
||||
local compare = require("sorting.compare")
|
||||
|
||||
---@class SortByColumn
|
||||
local M = {}
|
||||
|
||||
---Sort data by a column
|
||||
---@param data table[] Array of row data objects
|
||||
---@param column_id string Column identifier to sort by
|
||||
---@param direction SortDirection Sort direction ("asc" or "desc")
|
||||
---@return table[] Sorted data array (new array, original unchanged)
|
||||
function M.sortByColumn(data, column_id, direction)
|
||||
if not column_id or not direction then
|
||||
return data
|
||||
end
|
||||
|
||||
-- Create a copy to avoid mutating original
|
||||
local sorted = {}
|
||||
for i, row in ipairs(data) do
|
||||
sorted[i] = row
|
||||
end
|
||||
|
||||
table.sort(sorted, function(a, b)
|
||||
return compare.compare(a[column_id], b[column_id], direction)
|
||||
end)
|
||||
|
||||
return sorted
|
||||
end
|
||||
|
||||
return M
|
||||
33
packages/data_table/seed/scripts/sorting/toggle_sort.lua
Normal file
33
packages/data_table/seed/scripts/sorting/toggle_sort.lua
Normal file
@@ -0,0 +1,33 @@
|
||||
-- Toggle sort state for a column
|
||||
-- Single function module for data table sorting
|
||||
|
||||
---@class ToggleSort
|
||||
local M = {}
|
||||
|
||||
---Toggle sort state for a column
|
||||
---@param state SortState Current sort state
|
||||
---@param column_id string Column identifier clicked
|
||||
---@return SortState New sort state
|
||||
function M.toggleSort(state, column_id)
|
||||
if state.column_id ~= column_id then
|
||||
-- New column: start with ascending
|
||||
return {
|
||||
column_id = column_id,
|
||||
direction = "asc"
|
||||
}
|
||||
elseif state.direction == "asc" then
|
||||
-- Same column, was ascending: switch to descending
|
||||
return {
|
||||
column_id = column_id,
|
||||
direction = "desc"
|
||||
}
|
||||
else
|
||||
-- Same column, was descending: clear sort
|
||||
return {
|
||||
column_id = nil,
|
||||
direction = nil
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
14
packages/data_table/seed/scripts/sorting/types.lua
Normal file
14
packages/data_table/seed/scripts/sorting/types.lua
Normal file
@@ -0,0 +1,14 @@
|
||||
-- Type definitions for sorting module
|
||||
-- Shared across all sorting functions
|
||||
|
||||
---@alias SortDirection "asc" | "desc" | nil
|
||||
|
||||
---@class SortState
|
||||
---@field column_id string|nil Column being sorted
|
||||
---@field direction SortDirection Sort direction
|
||||
|
||||
---@class SortedResult
|
||||
---@field data table[] Sorted data array
|
||||
---@field state SortState Current sort state
|
||||
|
||||
return {}
|
||||
@@ -1,95 +1,5 @@
|
||||
---@class Fields
|
||||
local M = {}
|
||||
-- Form field builders
|
||||
-- DEPRECATED: This file redirects to fields/ directory
|
||||
-- Functions are now split into single-function files
|
||||
|
||||
---@class UIComponent
|
||||
---@field type string
|
||||
---@field props? table
|
||||
---@field children? UIComponent[]
|
||||
|
||||
---@class TextFieldProps
|
||||
---@field name string
|
||||
---@field label? string
|
||||
---@field placeholder? string
|
||||
---@field required? boolean
|
||||
|
||||
---@class EmailFieldProps
|
||||
---@field name string
|
||||
---@field label? string
|
||||
|
||||
---@class PasswordFieldProps
|
||||
---@field name string
|
||||
---@field label? string
|
||||
|
||||
---@class NumberFieldProps
|
||||
---@field name string
|
||||
---@field label? string
|
||||
---@field min? number
|
||||
---@field max? number
|
||||
|
||||
---@class TextAreaFieldProps
|
||||
---@field name string
|
||||
---@field label? string
|
||||
---@field placeholder? string
|
||||
---@field rows? number
|
||||
|
||||
---@param props TextFieldProps
|
||||
---@return UIComponent
|
||||
function M.text(props)
|
||||
return {
|
||||
type = "Box",
|
||||
children = {
|
||||
props.label and { type = "Label", props = { text = props.label, htmlFor = props.name } } or nil,
|
||||
{ type = "Input", props = { name = props.name, placeholder = props.placeholder, required = props.required } }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
---@param props EmailFieldProps
|
||||
---@return UIComponent
|
||||
function M.email(props)
|
||||
return {
|
||||
type = "Box",
|
||||
children = {
|
||||
{ type = "Label", props = { text = props.label or "Email", htmlFor = props.name } },
|
||||
{ type = "Input", props = { name = props.name, type = "email", placeholder = "you@example.com" } }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
---@param props PasswordFieldProps
|
||||
---@return UIComponent
|
||||
function M.password(props)
|
||||
return {
|
||||
type = "Box",
|
||||
children = {
|
||||
{ type = "Label", props = { text = props.label or "Password", htmlFor = props.name } },
|
||||
{ type = "Input", props = { name = props.name, type = "password", placeholder = "••••••••" } }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
---@param props NumberFieldProps
|
||||
---@return UIComponent
|
||||
function M.number(props)
|
||||
return {
|
||||
type = "Box",
|
||||
children = {
|
||||
props.label and { type = "Label", props = { text = props.label, htmlFor = props.name } } or nil,
|
||||
{ type = "Input", props = { name = props.name, type = "number", min = props.min, max = props.max } }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
---@param props TextAreaFieldProps
|
||||
---@return UIComponent
|
||||
function M.textarea(props)
|
||||
return {
|
||||
type = "Box",
|
||||
children = {
|
||||
props.label and { type = "Label", props = { text = props.label, htmlFor = props.name } } or nil,
|
||||
{ type = "TextArea", props = { name = props.name, rows = props.rows or 4, placeholder = props.placeholder } }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
return M
|
||||
return require("fields.init")
|
||||
|
||||
@@ -1,81 +1,5 @@
|
||||
---@class Validate
|
||||
local M = {}
|
||||
-- Form validation utilities
|
||||
-- DEPRECATED: This file redirects to validate/ directory
|
||||
-- Functions are now split into single-function files
|
||||
|
||||
---@class ValidationRule
|
||||
---@field type string
|
||||
---@field message? string
|
||||
---@field min? number
|
||||
---@field max? number
|
||||
---@field value? any
|
||||
|
||||
---@class ValidationResult
|
||||
---@field valid boolean
|
||||
---@field errors string[]
|
||||
|
||||
---@param value string|number|nil
|
||||
---@return boolean
|
||||
function M.required(value)
|
||||
return value ~= nil and value ~= ""
|
||||
end
|
||||
|
||||
---@param value string|nil
|
||||
---@return boolean
|
||||
function M.email(value)
|
||||
if not value then return false end
|
||||
return string.match(value, "^[^@]+@[^@]+%.[^@]+$") ~= nil
|
||||
end
|
||||
|
||||
---@param value string|nil
|
||||
---@param min number
|
||||
---@return boolean
|
||||
function M.minLength(value, min)
|
||||
return value and #value >= min
|
||||
end
|
||||
|
||||
---@param value string|nil
|
||||
---@param max number
|
||||
---@return boolean
|
||||
function M.maxLength(value, max)
|
||||
return not value or #value <= max
|
||||
end
|
||||
|
||||
---@param value string|nil
|
||||
---@param pat string
|
||||
---@return boolean
|
||||
function M.pattern(value, pat)
|
||||
return value and string.match(value, pat) ~= nil
|
||||
end
|
||||
|
||||
---@param value any
|
||||
---@return boolean
|
||||
function M.number(value)
|
||||
return tonumber(value) ~= nil
|
||||
end
|
||||
|
||||
---@param value any
|
||||
---@param min number
|
||||
---@param max number
|
||||
---@return boolean
|
||||
function M.range(value, min, max)
|
||||
local n = tonumber(value)
|
||||
return n and n >= min and n <= max
|
||||
end
|
||||
|
||||
---@param value string|number|nil
|
||||
---@param rules ValidationRule[]
|
||||
---@return ValidationResult
|
||||
function M.validate_field(value, rules)
|
||||
local errors = {}
|
||||
for _, rule in ipairs(rules) do
|
||||
if rule.type == "required" and not M.required(value) then
|
||||
errors[#errors + 1] = rule.message or "Required"
|
||||
elseif rule.type == "email" and not M.email(value) then
|
||||
errors[#errors + 1] = rule.message or "Invalid email"
|
||||
elseif rule.type == "minLength" and not M.minLength(value, rule.min) then
|
||||
errors[#errors + 1] = rule.message or ("Min " .. rule.min .. " chars")
|
||||
end
|
||||
end
|
||||
return { valid = #errors == 0, errors = errors }
|
||||
end
|
||||
|
||||
return M
|
||||
return require("validate.init")
|
||||
|
||||
Reference in New Issue
Block a user