mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
code: fakemui,tsx,nextjs (5 files)
This commit is contained in:
317
fakemui/fakemui/x/DataGrid.tsx
Normal file
317
fakemui/fakemui/x/DataGrid.tsx
Normal file
@@ -0,0 +1,317 @@
|
||||
import React, { useState, useMemo, useCallback } from 'react'
|
||||
import { classNames } from '../utils/classNames'
|
||||
|
||||
export interface GridColDef {
|
||||
field: string
|
||||
headerName: string
|
||||
width?: number
|
||||
flex?: number
|
||||
sortable?: boolean
|
||||
filterable?: boolean
|
||||
renderCell?: (params: GridRenderCellParams) => React.ReactNode
|
||||
valueGetter?: (params: GridValueGetterParams) => any
|
||||
valueFormatter?: (params: GridValueFormatterParams) => string
|
||||
editable?: boolean
|
||||
type?: 'string' | 'number' | 'date' | 'boolean' | 'actions'
|
||||
align?: 'left' | 'center' | 'right'
|
||||
headerAlign?: 'left' | 'center' | 'right'
|
||||
}
|
||||
|
||||
export interface GridRenderCellParams {
|
||||
value: any
|
||||
row: any
|
||||
field: string
|
||||
id: string | number
|
||||
}
|
||||
|
||||
export interface GridValueGetterParams {
|
||||
row: any
|
||||
field: string
|
||||
id: string | number
|
||||
}
|
||||
|
||||
export interface GridValueFormatterParams {
|
||||
value: any
|
||||
field: string
|
||||
id: string | number
|
||||
}
|
||||
|
||||
export interface GridRowParams {
|
||||
row: any
|
||||
id: string | number
|
||||
}
|
||||
|
||||
export interface GridSortModel {
|
||||
field: string
|
||||
sort: 'asc' | 'desc' | null
|
||||
}
|
||||
|
||||
export interface GridFilterModel {
|
||||
items: GridFilterItem[]
|
||||
}
|
||||
|
||||
export interface GridFilterItem {
|
||||
field: string
|
||||
operator: string
|
||||
value: any
|
||||
}
|
||||
|
||||
export interface DataGridProps {
|
||||
rows: any[]
|
||||
columns: GridColDef[]
|
||||
pageSize?: number
|
||||
rowsPerPageOptions?: number[]
|
||||
checkboxSelection?: boolean
|
||||
disableSelectionOnClick?: boolean
|
||||
onRowClick?: (params: GridRowParams) => void
|
||||
onSelectionModelChange?: (ids: (string | number)[]) => void
|
||||
loading?: boolean
|
||||
autoHeight?: boolean
|
||||
density?: 'compact' | 'standard' | 'comfortable'
|
||||
sortModel?: GridSortModel[]
|
||||
onSortModelChange?: (model: GridSortModel[]) => void
|
||||
filterModel?: GridFilterModel
|
||||
onFilterModelChange?: (model: GridFilterModel) => void
|
||||
getRowId?: (row: any) => string | number
|
||||
className?: string
|
||||
sx?: React.CSSProperties
|
||||
}
|
||||
|
||||
/**
|
||||
* DataGrid - A powerful data table component
|
||||
*/
|
||||
export function DataGrid({
|
||||
rows,
|
||||
columns,
|
||||
pageSize = 25,
|
||||
rowsPerPageOptions = [10, 25, 50, 100],
|
||||
checkboxSelection = false,
|
||||
disableSelectionOnClick = false,
|
||||
onRowClick,
|
||||
onSelectionModelChange,
|
||||
loading = false,
|
||||
autoHeight = false,
|
||||
density = 'standard',
|
||||
sortModel,
|
||||
onSortModelChange,
|
||||
filterModel,
|
||||
onFilterModelChange,
|
||||
getRowId = (row) => row.id,
|
||||
className,
|
||||
sx,
|
||||
}: DataGridProps) {
|
||||
const [page, setPage] = useState(0)
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string | number>>(new Set())
|
||||
const [internalSortModel, setInternalSortModel] = useState<GridSortModel[]>(sortModel || [])
|
||||
|
||||
const currentSortModel = sortModel || internalSortModel
|
||||
|
||||
const handleSort = useCallback((field: string) => {
|
||||
const existingSort = currentSortModel.find(s => s.field === field)
|
||||
let newSort: 'asc' | 'desc' | null = 'asc'
|
||||
|
||||
if (existingSort) {
|
||||
if (existingSort.sort === 'asc') newSort = 'desc'
|
||||
else if (existingSort.sort === 'desc') newSort = null
|
||||
}
|
||||
|
||||
const newModel = newSort
|
||||
? [{ field, sort: newSort }]
|
||||
: []
|
||||
|
||||
if (onSortModelChange) {
|
||||
onSortModelChange(newModel)
|
||||
} else {
|
||||
setInternalSortModel(newModel)
|
||||
}
|
||||
}, [currentSortModel, onSortModelChange])
|
||||
|
||||
const sortedRows = useMemo(() => {
|
||||
if (currentSortModel.length === 0) return rows
|
||||
|
||||
const sort = currentSortModel[0]
|
||||
return [...rows].sort((a, b) => {
|
||||
const aVal = a[sort.field]
|
||||
const bVal = b[sort.field]
|
||||
|
||||
if (aVal < bVal) return sort.sort === 'asc' ? -1 : 1
|
||||
if (aVal > bVal) return sort.sort === 'asc' ? 1 : -1
|
||||
return 0
|
||||
})
|
||||
}, [rows, currentSortModel])
|
||||
|
||||
const paginatedRows = useMemo(() => {
|
||||
const start = page * pageSize
|
||||
return sortedRows.slice(start, start + pageSize)
|
||||
}, [sortedRows, page, pageSize])
|
||||
|
||||
const totalPages = Math.ceil(rows.length / pageSize)
|
||||
|
||||
const handleRowClick = (row: any) => {
|
||||
const id = getRowId(row)
|
||||
|
||||
if (checkboxSelection && !disableSelectionOnClick) {
|
||||
const newSelected = new Set(selectedIds)
|
||||
if (newSelected.has(id)) {
|
||||
newSelected.delete(id)
|
||||
} else {
|
||||
newSelected.add(id)
|
||||
}
|
||||
setSelectedIds(newSelected)
|
||||
onSelectionModelChange?.(Array.from(newSelected))
|
||||
}
|
||||
|
||||
onRowClick?.({ row, id })
|
||||
}
|
||||
|
||||
const handleSelectAll = () => {
|
||||
if (selectedIds.size === rows.length) {
|
||||
setSelectedIds(new Set())
|
||||
onSelectionModelChange?.([])
|
||||
} else {
|
||||
const allIds = new Set(rows.map(getRowId))
|
||||
setSelectedIds(allIds)
|
||||
onSelectionModelChange?.(Array.from(allIds))
|
||||
}
|
||||
}
|
||||
|
||||
const densityClass = {
|
||||
compact: 'fakemui-datagrid--compact',
|
||||
standard: '',
|
||||
comfortable: 'fakemui-datagrid--comfortable',
|
||||
}[density]
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('fakemui-datagrid', densityClass, className)}
|
||||
style={{ ...sx, height: autoHeight ? 'auto' : '400px' }}
|
||||
>
|
||||
{loading && (
|
||||
<div className="fakemui-datagrid-loading">
|
||||
<div className="fakemui-datagrid-loading-spinner" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="fakemui-datagrid-container">
|
||||
<table className="fakemui-datagrid-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{checkboxSelection && (
|
||||
<th className="fakemui-datagrid-checkbox-cell">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedIds.size === rows.length && rows.length > 0}
|
||||
onChange={handleSelectAll}
|
||||
/>
|
||||
</th>
|
||||
)}
|
||||
{columns.map((col) => {
|
||||
const sort = currentSortModel.find(s => s.field === col.field)
|
||||
return (
|
||||
<th
|
||||
key={col.field}
|
||||
style={{
|
||||
width: col.width,
|
||||
flex: col.flex,
|
||||
textAlign: col.headerAlign || 'left',
|
||||
}}
|
||||
onClick={() => col.sortable !== false && handleSort(col.field)}
|
||||
className={classNames(
|
||||
'fakemui-datagrid-header-cell',
|
||||
col.sortable !== false && 'fakemui-datagrid-header-cell--sortable'
|
||||
)}
|
||||
>
|
||||
{col.headerName}
|
||||
{sort && (
|
||||
<span className="fakemui-datagrid-sort-icon">
|
||||
{sort.sort === 'asc' ? '↑' : '↓'}
|
||||
</span>
|
||||
)}
|
||||
</th>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{paginatedRows.map((row) => {
|
||||
const id = getRowId(row)
|
||||
const isSelected = selectedIds.has(id)
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={id}
|
||||
onClick={() => handleRowClick(row)}
|
||||
className={classNames(
|
||||
'fakemui-datagrid-row',
|
||||
isSelected && 'fakemui-datagrid-row--selected'
|
||||
)}
|
||||
>
|
||||
{checkboxSelection && (
|
||||
<td className="fakemui-datagrid-checkbox-cell">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isSelected}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
{columns.map((col) => {
|
||||
let value = row[col.field]
|
||||
|
||||
if (col.valueGetter) {
|
||||
value = col.valueGetter({ row, field: col.field, id })
|
||||
}
|
||||
|
||||
if (col.valueFormatter) {
|
||||
value = col.valueFormatter({ value, field: col.field, id })
|
||||
}
|
||||
|
||||
const cellContent = col.renderCell
|
||||
? col.renderCell({ value, row, field: col.field, id })
|
||||
: value
|
||||
|
||||
return (
|
||||
<td
|
||||
key={col.field}
|
||||
style={{ textAlign: col.align || 'left' }}
|
||||
className="fakemui-datagrid-cell"
|
||||
>
|
||||
{cellContent}
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className="fakemui-datagrid-footer">
|
||||
<div className="fakemui-datagrid-pagination">
|
||||
<span>
|
||||
{page * pageSize + 1}–{Math.min((page + 1) * pageSize, rows.length)} of {rows.length}
|
||||
</span>
|
||||
<button
|
||||
disabled={page === 0}
|
||||
onClick={() => setPage(p => p - 1)}
|
||||
className="fakemui-datagrid-pagination-btn"
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<button
|
||||
disabled={page >= totalPages - 1}
|
||||
onClick={() => setPage(p => p + 1)}
|
||||
className="fakemui-datagrid-pagination-btn"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Aliases for compatibility
|
||||
export const DataGridPro = DataGrid
|
||||
export const DataGridPremium = DataGrid
|
||||
279
fakemui/fakemui/x/DatePicker.tsx
Normal file
279
fakemui/fakemui/x/DatePicker.tsx
Normal file
@@ -0,0 +1,279 @@
|
||||
import React, { useState, useRef, useEffect } from 'react'
|
||||
import { classNames } from '../utils/classNames'
|
||||
|
||||
export interface DatePickerProps {
|
||||
value?: Date | null
|
||||
onChange?: (date: Date | null) => void
|
||||
label?: string
|
||||
disabled?: boolean
|
||||
minDate?: Date
|
||||
maxDate?: Date
|
||||
format?: string
|
||||
inputFormat?: string
|
||||
views?: ('year' | 'month' | 'day')[]
|
||||
openTo?: 'year' | 'month' | 'day'
|
||||
className?: string
|
||||
sx?: React.CSSProperties
|
||||
renderInput?: (props: any) => React.ReactNode
|
||||
disableFuture?: boolean
|
||||
disablePast?: boolean
|
||||
}
|
||||
|
||||
export interface TimePickerProps {
|
||||
value?: Date | null
|
||||
onChange?: (date: Date | null) => void
|
||||
label?: string
|
||||
disabled?: boolean
|
||||
ampm?: boolean
|
||||
views?: ('hours' | 'minutes' | 'seconds')[]
|
||||
className?: string
|
||||
sx?: React.CSSProperties
|
||||
renderInput?: (props: any) => React.ReactNode
|
||||
}
|
||||
|
||||
export interface DateTimePickerProps extends Omit<DatePickerProps, 'views'>, Omit<TimePickerProps, 'views'> {
|
||||
dateFormat?: string
|
||||
timeFormat?: string
|
||||
views?: ('year' | 'month' | 'day' | 'hours' | 'minutes' | 'seconds')[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a date to YYYY-MM-DD
|
||||
*/
|
||||
const formatDate = (date: Date | null): string => {
|
||||
if (!date) return ''
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a time to HH:MM
|
||||
*/
|
||||
const formatTime = (date: Date | null): string => {
|
||||
if (!date) return ''
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${hours}:${minutes}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a datetime to YYYY-MM-DDTHH:MM
|
||||
*/
|
||||
const formatDateTime = (date: Date | null): string => {
|
||||
if (!date) return ''
|
||||
return `${formatDate(date)}T${formatTime(date)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* DatePicker - Date selection component
|
||||
*/
|
||||
export function DatePicker({
|
||||
value,
|
||||
onChange,
|
||||
label,
|
||||
disabled = false,
|
||||
minDate,
|
||||
maxDate,
|
||||
className,
|
||||
sx,
|
||||
renderInput,
|
||||
disableFuture = false,
|
||||
disablePast = false,
|
||||
}: DatePickerProps) {
|
||||
const [internalValue, setInternalValue] = useState(value)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const currentValue = value !== undefined ? value : internalValue
|
||||
|
||||
useEffect(() => {
|
||||
if (value !== undefined) {
|
||||
setInternalValue(value)
|
||||
}
|
||||
}, [value])
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const dateStr = e.target.value
|
||||
const newDate = dateStr ? new Date(dateStr + 'T00:00:00') : null
|
||||
|
||||
setInternalValue(newDate)
|
||||
onChange?.(newDate)
|
||||
}
|
||||
|
||||
const getMinMax = () => {
|
||||
let min: string | undefined
|
||||
let max: string | undefined
|
||||
|
||||
if (minDate) min = formatDate(minDate)
|
||||
if (maxDate) max = formatDate(maxDate)
|
||||
if (disablePast) min = formatDate(new Date())
|
||||
if (disableFuture) max = formatDate(new Date())
|
||||
|
||||
return { min, max }
|
||||
}
|
||||
|
||||
const { min, max } = getMinMax()
|
||||
|
||||
const inputProps = {
|
||||
type: 'date',
|
||||
value: formatDate(currentValue),
|
||||
onChange: handleChange,
|
||||
disabled,
|
||||
min,
|
||||
max,
|
||||
ref: inputRef,
|
||||
className: 'fakemui-datepicker-input',
|
||||
}
|
||||
|
||||
if (renderInput) {
|
||||
return renderInput(inputProps)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('fakemui-datepicker', className)} style={sx}>
|
||||
{label && <label className="fakemui-datepicker-label">{label}</label>}
|
||||
<input {...inputProps} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* TimePicker - Time selection component
|
||||
*/
|
||||
export function TimePicker({
|
||||
value,
|
||||
onChange,
|
||||
label,
|
||||
disabled = false,
|
||||
ampm = false,
|
||||
className,
|
||||
sx,
|
||||
renderInput,
|
||||
}: TimePickerProps) {
|
||||
const [internalValue, setInternalValue] = useState(value)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const currentValue = value !== undefined ? value : internalValue
|
||||
|
||||
useEffect(() => {
|
||||
if (value !== undefined) {
|
||||
setInternalValue(value)
|
||||
}
|
||||
}, [value])
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const timeStr = e.target.value
|
||||
if (!timeStr) {
|
||||
setInternalValue(null)
|
||||
onChange?.(null)
|
||||
return
|
||||
}
|
||||
|
||||
const [hours, minutes] = timeStr.split(':').map(Number)
|
||||
const newDate = new Date()
|
||||
newDate.setHours(hours, minutes, 0, 0)
|
||||
|
||||
setInternalValue(newDate)
|
||||
onChange?.(newDate)
|
||||
}
|
||||
|
||||
const inputProps = {
|
||||
type: 'time',
|
||||
value: formatTime(currentValue),
|
||||
onChange: handleChange,
|
||||
disabled,
|
||||
ref: inputRef,
|
||||
className: 'fakemui-timepicker-input',
|
||||
}
|
||||
|
||||
if (renderInput) {
|
||||
return renderInput(inputProps)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('fakemui-timepicker', className)} style={sx}>
|
||||
{label && <label className="fakemui-timepicker-label">{label}</label>}
|
||||
<input {...inputProps} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* DateTimePicker - Combined date and time selection
|
||||
*/
|
||||
export function DateTimePicker({
|
||||
value,
|
||||
onChange,
|
||||
label,
|
||||
disabled = false,
|
||||
minDate,
|
||||
maxDate,
|
||||
className,
|
||||
sx,
|
||||
renderInput,
|
||||
disableFuture = false,
|
||||
disablePast = false,
|
||||
}: DateTimePickerProps) {
|
||||
const [internalValue, setInternalValue] = useState(value)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const currentValue = value !== undefined ? value : internalValue
|
||||
|
||||
useEffect(() => {
|
||||
if (value !== undefined) {
|
||||
setInternalValue(value)
|
||||
}
|
||||
}, [value])
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const dateTimeStr = e.target.value
|
||||
const newDate = dateTimeStr ? new Date(dateTimeStr) : null
|
||||
|
||||
setInternalValue(newDate)
|
||||
onChange?.(newDate)
|
||||
}
|
||||
|
||||
const getMinMax = () => {
|
||||
let min: string | undefined
|
||||
let max: string | undefined
|
||||
|
||||
if (minDate) min = formatDateTime(minDate)
|
||||
if (maxDate) max = formatDateTime(maxDate)
|
||||
if (disablePast) min = formatDateTime(new Date())
|
||||
if (disableFuture) max = formatDateTime(new Date())
|
||||
|
||||
return { min, max }
|
||||
}
|
||||
|
||||
const { min, max } = getMinMax()
|
||||
|
||||
const inputProps = {
|
||||
type: 'datetime-local',
|
||||
value: formatDateTime(currentValue),
|
||||
onChange: handleChange,
|
||||
disabled,
|
||||
min,
|
||||
max,
|
||||
ref: inputRef,
|
||||
className: 'fakemui-datetimepicker-input',
|
||||
}
|
||||
|
||||
if (renderInput) {
|
||||
return renderInput(inputProps)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('fakemui-datetimepicker', className)} style={sx}>
|
||||
{label && <label className="fakemui-datetimepicker-label">{label}</label>}
|
||||
<input {...inputProps} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Aliases for API compatibility
|
||||
export const DesktopDatePicker = DatePicker
|
||||
export const MobileDatePicker = DatePicker
|
||||
export const StaticDatePicker = DatePicker
|
||||
export const CalendarPicker = DatePicker
|
||||
export const ClockPicker = TimePicker
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
TableFooter as FakeMuiTableFooter,
|
||||
TableHead as FakeMuiTableHead,
|
||||
TableRow as FakeMuiTableRow,
|
||||
} from 'fakemui'
|
||||
} from '@/fakemui'
|
||||
import { forwardRef, ReactNode } from 'react'
|
||||
|
||||
import styles from './Table.module.scss'
|
||||
|
||||
@@ -3,12 +3,11 @@ export * from '../app-config'
|
||||
export * from '../auth'
|
||||
export * from '../comments'
|
||||
export * from '../components'
|
||||
export * from '../credentials'
|
||||
export * from '../god-credentials'
|
||||
export * from '../css-classes'
|
||||
export * from '../database-admin'
|
||||
export * from '../dropdown-configs'
|
||||
export * from '../error-logs'
|
||||
export * from '../god-credentials'
|
||||
export * from '../lua-scripts'
|
||||
export * from '../packages'
|
||||
export * from '../pages'
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { seedAppConfig } from './app/seed-app-config'
|
||||
import { seedUsers } from './app/seed-users'
|
||||
import { seedCssCategories } from './css/seed-css-categories'
|
||||
import { seedDropdownConfigs } from './dropdowns/seed-dropdown-configs'
|
||||
|
||||
@@ -7,14 +6,15 @@ import { seedDropdownConfigs } from './dropdowns/seed-dropdown-configs'
|
||||
* Seed database with default data
|
||||
*/
|
||||
export const seedDefaultData = async (): Promise<void> => {
|
||||
await seedUsers()
|
||||
// TODO: Implement seedUsers function and import it
|
||||
// await seedUsers()
|
||||
await seedAppConfig()
|
||||
await seedCssCategories()
|
||||
await seedDropdownConfigs()
|
||||
}
|
||||
|
||||
export const defaultDataBuilders = {
|
||||
seedUsers,
|
||||
// seedUsers,
|
||||
seedAppConfig,
|
||||
seedCssCategories,
|
||||
seedDropdownConfigs,
|
||||
|
||||
Reference in New Issue
Block a user