Compare commits

..

15 Commits

Author SHA1 Message Date
a896cdf223 Implement search functionality for models by label in SpotlightContext and update list data handling to include model actions. Enhance model retrieval logic for improved user interaction in the dashboard.
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
2026-06-25 01:00:33 +01:00
2df25364a0 Add URLs to various database models for improved navigation in the dashboard 2026-06-25 00:50:59 +01:00
75eeed16a0 Add plural labels to various database models 2026-06-25 00:47:12 +01:00
tom
02b0d245ff Adjust margin properties in DashboardNavigation and DashboardWindowButtons components for improved layout consistency and spacing.
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
2026-06-24 00:32:58 +01:00
tom
86d656720c Update dev:electron script in package.json for improved environment variable handling and add msiWrapped configuration for Windows installer.
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
2026-06-24 00:06:00 +01:00
tom
1474b23b1e Fixed windows update installation.
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
2026-06-23 23:20:19 +01:00
657c1dd17e Add expandHeight prop to various Dashboard components for improved layout flexibility
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
2026-06-22 01:48:12 +01:00
2c3cf9d02b Update various components in the Dashboard to include labelWidth adjustments for improved layout consistency. Components modified include NewAppPassword, NewCourierService, NewCourier, NewDocumentJob, NewDocumentSize, NewDocumentTemplate, NewMaterial, NewNoteType, NewProductCategory, NewProductSku, NewTaxRate, and NewVendor. These changes enhance user experience by ensuring better alignment and spacing in forms.
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
2026-06-22 00:41:04 +01:00
2177870fc9 Enhance Filament and FilamentSku models by enforcing required fields for cost-related properties. Update NewFilamentSku and NewFilament components to reflect changes in default values and layout adjustments for better user experience. Modify FilamentInfo to utilize updated object data structure for filament selection.
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
2026-06-21 23:59:21 +01:00
0e8f4506cc Update padding in DashboardNavigation component to enhance layout consistency and improve user interaction. 2026-06-21 23:45:03 +01:00
8568a8a8ce Refactor AppUpdateProgress to conditionally display progress for the restart stage. Remove unnecessary detail display for the restart status, enhancing clarity in update management.
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
2026-06-21 23:42:11 +01:00
991c10d48a Refactor DashboardNavigation and WebAppSwitcher components to improve layout and user interaction. Adjust padding in DashboardNavigation and replace Tooltip with KeyboardShortcut in WebAppSwitcher for enhanced accessibility and streamlined code. 2026-06-21 23:40:45 +01:00
8fbcc67230 Refactor Part and Product models to change 'disabled' property to 'readOnly' for margin fields. Implement value calculation for margin based on price and cost when priceMode is 'amount', enhancing data handling and user experience.
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
2026-06-21 23:30:36 +01:00
156d2a8baf Update Part model to enforce required fields for cost, costWithTax, priceMode, price, margin, priceWithTax, and priceTaxRate. Remove fileName property and improve filter and sorter organization for better clarity and maintainability.
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
2026-06-21 23:13:25 +01:00
8677691da3 Enhance isInstallComplete function in AppUpdateProgress to recognize additional completion messages, including 'successful' and 'restarting', improving accuracy of installation status detection during updates.
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
2026-06-21 22:54:58 +01:00
113 changed files with 802 additions and 159 deletions

View File

@ -80,7 +80,7 @@
"electron": "cross-env ELECTRON_START_URL=http://0.0.0.0:5780 && cross-env NODE_ENV=development && electron .",
"start": "serve -s build",
"build": "vite build",
"dev:electron": "concurrently \"cross-env NODE_ENV=development vite --port 5780 --no-open\" \"cross-env ELECTRON_START_URL=http://localhost:5780 cross-env NODE_ENV=development electron public/electron.js\"",
"dev:electron": "concurrently \"cross-env NODE_ENV=development vite --port 5780 --no-open\" \"cross-env ELECTRON_START_URL=http://localhost:5780 NODE_ENV=development electron public/electron.js\"",
"build:electron": "vite build && electron-builder",
"build:cloudflare": "cross-env VITE_DEPLOY_TARGET=cloudflare vite build",
"deploy": "npm run build:cloudflare && wrangler pages deploy --branch main"
@ -213,7 +213,7 @@
"win": {
"target": [
"nsis",
"msi"
"msiWrapped"
],
"protocols": [
{
@ -232,6 +232,12 @@
"allowToChangeInstallationDirectory": true,
"include": "scripts/installer.nsh",
"perMachine": true
},
"msiWrapped": {
"upgradeCode": "{735812DB-E33B-57A0-8FBC-5FC3155925AA}",
"perMachine": true,
"impersonate": false,
"wrappedInstallerArgs": "/S"
}
}
}

View File

@ -1,82 +1,408 @@
import { spawn } from 'child_process'
import { promises as fs } from 'fs'
import os from 'os'
import path from 'path'
import process from 'process'
const buildWindowsInstallCommand = (installerPath) => ({
command: 'cmd.exe',
args: [
'/d',
'/s',
'/c',
`timeout /t 2 /nobreak >NUL && msiexec.exe /i "${installerPath}" /qn /norestart`
]
})
const MSI_OLE_HEADER = Buffer.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1])
const DEBUG_PREFIX = '[app-update][win-progress]'
export const launchWindowsInstaller = (
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
const debugLog = (message, details) => {
if (details === undefined) {
console.log(`${DEBUG_PREFIX} ${message}`)
return
}
console.log(`${DEBUG_PREFIX} ${message}`, details)
}
const decodeMsiLogBuffer = (buffer) => {
if (!buffer?.length) return ''
if (buffer.length >= 2 && buffer[0] === 0xff && buffer[1] === 0xfe) {
debugLog('decoded MSI log as UTF-16 LE (BOM)')
return buffer.subarray(2).toString('utf16le')
}
const sample = buffer.subarray(0, Math.min(buffer.length, 64))
const looksUtf16 =
sample.length >= 4 &&
sample.filter((byte) => byte === 0).length > sample.length / 4
if (looksUtf16) {
debugLog('decoded MSI log as UTF-16 LE (heuristic)')
return buffer.toString('utf16le')
}
debugLog('decoded MSI log as UTF-8')
return buffer.toString('utf8')
}
const formatMsiActionName = (actionName) => {
const humanized = String(actionName)
.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/_/g, ' ')
.toLowerCase()
.trim()
if (!humanized) return 'Installing update...'
return `${humanized.charAt(0).toUpperCase()}${humanized.slice(1)}...`
}
const parseWindowsInstallerProgress = (output) => {
const lines = String(output || '').split(/\r?\n/)
let percent = null
let message = 'Installing update...'
let totalTicks = 0
let currentTicks = 0
let actionStarts = 0
let actionEnds = 0
const matchedLines = []
for (const line of lines) {
const actionStart = line.match(/^Action start \d{2}:\d{2}:\d{2}: (.+?)\./)
if (actionStart) {
actionStarts += 1
message = formatMsiActionName(actionStart[1])
matchedLines.push(`action-start:${actionStart[1]}`)
}
const doingAction = line.match(/Doing action:\s*(.+)$/)
if (doingAction && !actionStart) {
message = formatMsiActionName(doingAction[1])
matchedLines.push(`doing-action:${doingAction[1]}`)
}
if (/^Action ended \d{2}:\d{2}:\d{2}: .+?\. Return value \d+\./.test(line)) {
actionEnds += 1
matchedLines.push('action-ended')
}
const progressReset = line.match(/^\s*0\s+(\d+)\s+0(?:\s+\d+)?\s*$/)
if (progressReset) {
totalTicks = Number.parseInt(progressReset[1], 10) || 0
currentTicks = 0
matchedLines.push(`progress-reset:${totalTicks}`)
}
const progressIncrement = line.match(/^\s*2\s+(\d+)\s*$/)
if (progressIncrement) {
currentTicks += Number.parseInt(progressIncrement[1], 10) || 0
matchedLines.push(`progress-increment:${progressIncrement[1]}`)
}
const progressAddition = line.match(/^\s*3\s+(\d+)\s*$/)
if (progressAddition) {
totalTicks += Number.parseInt(progressAddition[1], 10) || 0
matchedLines.push(`progress-addition:${progressAddition[1]}`)
}
if (/Installation success or error status:\s*0\b/.test(line)) {
percent = 100
message = 'Installation complete. Restarting Farm Control...'
matchedLines.push('install-success')
}
}
if (percent !== 100) {
if (totalTicks > 0) {
percent = Math.min(99, Math.round((currentTicks / totalTicks) * 100))
} else if (actionStarts > 0) {
percent = Math.min(
95,
Math.max(5, Math.round((actionEnds / actionStarts) * 90))
)
}
}
return {
percent,
message,
stats: {
lineCount: lines.length,
actionStarts,
actionEnds,
totalTicks,
currentTicks,
matchedLines: matchedLines.slice(-8)
}
}
}
const isWindowsInstallSuccessful = (output) =>
/Installation success or error status:\s*0\b/.test(output) ||
/MainEngineThread is returning 0\b/.test(output)
const isWindowsInstallFailed = (output) =>
/Installation success or error status:\s*[1-9]\d*\b/.test(output) ||
/MainEngineThread is returning [1-9]\d*\b/.test(output)
const isValidMsiPackage = async (filePath) => {
const handle = await fs.open(filePath, 'r')
try {
const header = Buffer.alloc(MSI_OLE_HEADER.length)
await handle.read(header, 0, header.length, 0)
return header.equals(MSI_OLE_HEADER)
} finally {
await handle.close()
}
}
const prepareInstallerPath = async (installerPath) => {
const fileName = path.basename(installerPath)
const updateDir = path.join(
process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
'FarmControl',
'Updates'
)
await fs.mkdir(updateDir, { recursive: true })
const stablePath = path.join(updateDir, fileName)
await fs.copyFile(installerPath, stablePath)
// Resolve to a canonical long path. Short 8.3 paths (e.g. ADMINI~1) break msiexec.
const resolvedPath = await fs.realpath(stablePath)
const stats = await fs.stat(resolvedPath)
if (!stats.isFile() || stats.size === 0) {
throw new Error('Update installer file is missing or empty.')
}
if (!(await isValidMsiPackage(resolvedPath))) {
throw new Error(
'Downloaded update is not a valid Windows Installer package. The file may be corrupted or incomplete.'
)
}
return resolvedPath
}
const startWindowsInstallerProgressWatch = (
logPath,
webContents,
sendProgress
) => {
let installerOutput = ''
let lastLogSize = 0
let lastPercent = null
let lastMessage = null
let pollCount = 0
const poll = async () => {
pollCount += 1
try {
const stat = await fs.stat(logPath)
if (stat.size === 0) {
debugLog(`poll #${pollCount}: log exists but is empty`, { logPath })
return
}
if (stat.size === lastLogSize) {
debugLog(`poll #${pollCount}: no new log data`, {
logPath,
size: stat.size
})
return
}
const buffer = Buffer.alloc(stat.size)
const handle = await fs.open(logPath, 'r')
try {
await handle.read(buffer, 0, stat.size, 0)
} finally {
await handle.close()
}
lastLogSize = stat.size
installerOutput = decodeMsiLogBuffer(buffer)
const { percent, message, stats } =
parseWindowsInstallerProgress(installerOutput)
const resolvedPercent = percent ?? lastPercent ?? 0
const resolvedMessage = message || 'Installing update...'
debugLog(`poll #${pollCount}: parsed installer log`, {
logPath,
size: stat.size,
textLength: installerOutput.length,
preview: installerOutput.slice(0, 240).replace(/\s+/g, ' '),
parsed: stats,
resolvedPercent,
resolvedMessage
})
if (
resolvedPercent !== lastPercent ||
resolvedMessage !== lastMessage
) {
debugLog(`poll #${pollCount}: sending progress update`, {
percent: resolvedPercent,
message: resolvedMessage
})
lastPercent = resolvedPercent
lastMessage = resolvedMessage
sendProgress(webContents, {
phase: 'installing',
percent: resolvedPercent,
message: resolvedMessage
})
} else {
debugLog(`poll #${pollCount}: progress unchanged, skipping UI update`, {
percent: resolvedPercent,
message: resolvedMessage
})
}
} catch (error) {
if (error?.code === 'ENOENT') {
debugLog(`poll #${pollCount}: log file not created yet`, { logPath })
return
}
console.error(`${DEBUG_PREFIX} installer log poll error:`, error)
}
}
const intervalId = setInterval(() => {
poll().catch((error) => {
console.error(`${DEBUG_PREFIX} installer log poll error:`, error)
})
}, 300)
return async () => {
clearInterval(intervalId)
await poll()
debugLog('stopped progress watch', {
logPath,
finalSize: lastLogSize,
textLength: installerOutput.length,
pollCount
})
return installerOutput
}
}
export const launchWindowsInstaller = async (
app,
installerPath,
webContents,
{ sendProgress, getInstallErrorMessage }
) => {
const { command, args } = buildWindowsInstallCommand(installerPath)
const resolvedPath = await prepareInstallerPath(installerPath)
const logPath = path.join(path.dirname(resolvedPath), 'install.log')
console.log('[app-update] launching installer:', {
debugLog('prepared installer', {
installerPath,
command,
args,
shellCommand: args.join(' '),
platform: process.platform
resolvedPath,
logPath
})
sendProgress(webContents, {
phase: 'installing',
percent: 100,
message: 'Installing update. Farm Control will restart automatically.'
percent: 0,
message: 'Installing update...'
})
return new Promise((resolve, reject) => {
let installerOutput = ''
await fs.unlink(logPath).catch(() => {})
const installerProcess = spawn(command, args, {
detached: true,
// Allow file handles from the download/copy to settle before msiexec opens the MSI.
await sleep(2000)
const stopProgressWatch = startWindowsInstallerProgressWatch(
logPath,
webContents,
sendProgress
)
return new Promise((resolve, reject) => {
let processOutput = ''
const startedAt = Date.now()
const installerArgs = [
'/i',
resolvedPath,
'/qn',
'/norestart',
'/L*v!',
logPath
]
debugLog('spawning msiexec', {
args: installerArgs,
elapsedMs: Date.now() - startedAt
})
const installerProcess = spawn('msiexec.exe', installerArgs, {
stdio: ['ignore', 'pipe', 'pipe'],
windowsHide: true
})
installerProcess.stdout?.on('data', (data) => {
const text = data.toString()
installerOutput += text
console.log('[app-update] installer stdout:', text)
const text = data.toString('utf16le')
processOutput += text
debugLog('msiexec stdout chunk', {
length: text.length,
preview: text.slice(0, 200)
})
})
installerProcess.stderr?.on('data', (data) => {
const text = data.toString()
installerOutput += text
console.error('[app-update] installer stderr:', text)
const text = data.toString('utf16le')
processOutput += text
debugLog('msiexec stderr chunk', {
length: text.length,
preview: text.slice(0, 200)
})
})
installerProcess.on('spawn', () => {
console.log('[app-update] installer spawned, pid:', installerProcess.pid)
debugLog('msiexec spawned', {
pid: installerProcess.pid,
elapsedMs: Date.now() - startedAt
})
})
installerProcess.on('error', (error) => {
console.error('[app-update] installer spawn error:', error)
installerProcess.on('error', async (error) => {
console.error(`${DEBUG_PREFIX} installer spawn error:`, error)
const watchedOutput = await stopProgressWatch()
debugLog('installer spawn failed', {
watchedOutputLength: watchedOutput.length,
processOutputLength: processOutput.length
})
const message = error?.message || 'Failed to start update installer.'
sendProgress(webContents, {
phase: 'error',
percent: null,
message: error?.message || 'Failed to start update installer.'
message
})
reject(error)
})
installerProcess.on('exit', (code, signal) => {
console.log('[app-update] installer exited:', {
installerProcess.on('exit', async (code, signal) => {
const watchedOutput = await stopProgressWatch()
const output = watchedOutput || processOutput
const finalParse = parseWindowsInstallerProgress(output)
debugLog('msiexec exited', {
code,
signal,
output: installerOutput
elapsedMs: Date.now() - startedAt,
watchedOutputLength: watchedOutput.length,
processOutputLength: processOutput.length,
parsed: finalParse.stats,
outputPreview: output.slice(0, 500).replace(/\s+/g, ' ')
})
debugLog('keeping install log', { logPath })
if (code !== 0) {
const message = getInstallErrorMessage(null, installerOutput)
const message = getInstallErrorMessage(null, output)
sendProgress(webContents, {
phase: 'error',
percent: null,
@ -86,14 +412,38 @@ export const launchWindowsInstaller = (
return
}
sendProgress(webContents, {
phase: 'installing',
percent: 100,
message: 'Installation complete. Restarting Farm Control...'
})
resolve()
const succeeded =
isWindowsInstallSuccessful(output) ||
(code === 0 && !isWindowsInstallFailed(output))
debugLog('install success evaluation', {
succeeded,
isSuccessful: isWindowsInstallSuccessful(output),
isFailed: isWindowsInstallFailed(output),
exitCode: code
})
installerProcess.unref()
if (!succeeded) {
const message = getInstallErrorMessage(null, output)
sendProgress(webContents, {
phase: 'error',
percent: null,
message
})
reject(new Error(message))
return
}
const { percent, message } = finalParse
sendProgress(webContents, {
phase: 'installing',
percent: percent ?? 100,
message: message || 'Installation complete. Restarting Farm Control...'
})
debugLog('installer completed successfully')
resolve()
})
})
}

View File

@ -80,6 +80,7 @@ const Invoices = () => {
type='invoice'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -80,6 +80,7 @@ const Payments = () => {
type='payment'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal
@ -105,4 +106,3 @@ const Payments = () => {
}
export default Payments

View File

@ -80,6 +80,7 @@ const TaxRecords = () => {
type='taxRecord'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -86,6 +86,7 @@ const FilamentStocks = () => {
type='filamentStock'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -80,6 +80,7 @@ const OrderItems = () => {
type='orderItem'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -86,6 +86,7 @@ const PartStocks = () => {
type='partStock'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -87,6 +87,7 @@ const ProductStocks = () => {
type='productStock'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -80,6 +80,7 @@ const PurchaseOrders = () => {
type='purchaseOrder'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -80,6 +80,7 @@ const Shipments = () => {
type='shipment'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -86,6 +86,7 @@ const StockAudits = () => {
type='stockAudit'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -69,6 +69,7 @@ const StockEvents = () => {
type='stockEvent'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
</>

View File

@ -84,6 +84,7 @@ const StockLocations = () => {
type='stockLocation'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -84,6 +84,7 @@ const StockTransfers = () => {
type='stockTransfer'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -80,6 +80,7 @@ const AppPasswords = () => {
cards={viewMode === 'cards'}
visibleColumns={columnVisibility}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
<Modal

View File

@ -24,25 +24,12 @@ const NewAppPassword = ({ onOk, reset, defaultValues = {} }) => {
column={1}
bordered={false}
isEditing={true}
labelWidth={75}
required={true}
objectData={objectData}
/>
)
},
{
title: 'Optional',
key: 'optional',
content: (
<ObjectInfo
type='appPassword'
column={1}
bordered={false}
isEditing={true}
required={false}
objectData={objectData}
/>
)
},
{
title: 'Summary',
key: 'summary',
@ -59,6 +46,7 @@ const NewAppPassword = ({ onOk, reset, defaultValues = {} }) => {
secret: false
}}
isEditing={false}
labelWidth={70}
objectData={objectData}
/>
)

View File

@ -72,11 +72,17 @@ const getDownloadStageStatus = (phase, isError) => {
return 'pending'
}
const isInstallComplete = (phase, message) =>
phase === 'installing' &&
String(message || '')
.toLowerCase()
.includes('complete')
const isInstallComplete = (phase, message) => {
if (phase !== 'installing') return false
const normalized = String(message || '').toLowerCase()
return (
normalized.includes('complete') ||
normalized.includes('successful') ||
normalized.includes('restarting')
)
}
const getInstallStageStatus = (phase, isError, message) => {
if (isError && ['downloaded', 'installing'].includes(phase)) return 'error'
@ -106,7 +112,7 @@ const UpdateStage = ({ stage, status, percent, detail }) => {
const resolvedStatus =
status !== 'error' && resolvedPercent === 100 ? 'complete' : status
const color = getStageColor(resolvedStatus, token)
const showProgress = resolvedStatus === 'active'
const showProgress = resolvedStatus === 'active' && stage !== 'restart'
const StatusIcon =
resolvedStatus === 'complete'
@ -173,8 +179,6 @@ const AppUpdateProgress = ({ progress, update, onClose }) => {
const installDetail = installStatus === 'active' ? message : null
const restartDetail = restartStatus === 'active' ? message : null
return (
<Flex vertical gap='middle'>
<Text>
@ -197,11 +201,7 @@ const AppUpdateProgress = ({ progress, update, onClose }) => {
percent={installPercent}
detail={installDetail}
/>
<UpdateStage
stage='restart'
status={restartStatus}
detail={restartDetail}
/>
<UpdateStage stage='restart' status={restartStatus} />
</Flex>
<Modal

View File

@ -62,6 +62,7 @@ const AuditLogs = () => {
visibleColumns={columnVisibility}
type='auditLog'
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
</>

View File

@ -80,6 +80,7 @@ const CourierServices = () => {
type='courierService'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -26,6 +26,7 @@ const NewCourierService = ({ onOk, defaultValues }) => {
isEditing={true}
required={true}
objectData={objectData}
labelWidth={80}
/>
)
},
@ -39,6 +40,7 @@ const NewCourierService = ({ onOk, defaultValues }) => {
bordered={false}
isEditing={true}
required={false}
labelWidth={120}
objectData={objectData}
/>
)
@ -59,6 +61,7 @@ const NewCourierService = ({ onOk, defaultValues }) => {
}}
isEditing={false}
objectData={objectData}
labelWidth={120}
/>
)
}

View File

@ -79,6 +79,7 @@ const Couriers = () => {
type='courier'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -19,6 +19,7 @@ const NewCourier = ({ onOk, defaultValues }) => {
isEditing={true}
required={true}
objectData={objectData}
labelWidth={75}
/>
)
},
@ -33,6 +34,7 @@ const NewCourier = ({ onOk, defaultValues }) => {
isEditing={true}
required={false}
objectData={objectData}
labelWidth={85}
/>
)
},
@ -52,6 +54,7 @@ const NewCourier = ({ onOk, defaultValues }) => {
}}
isEditing={false}
objectData={objectData}
labelWidth={85}
/>
)
}

View File

@ -80,6 +80,7 @@ const DocumentJobs = () => {
type='documentJob'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -35,6 +35,7 @@ const NewDocumentJob = ({ onOk, defaultValues = {} }) => {
column={1}
visibleProperties={{ name: false }}
bordered={false}
labelWidth={115}
isEditing={true}
required={true}
objectData={objectData}

View File

@ -79,6 +79,7 @@ const DocumentPrinters = () => {
type='documentPrinter'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -78,6 +78,7 @@ const DocumentSizes = () => {
type='documentSize'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -22,6 +22,7 @@ const NewDocumentSize = ({ onOk, defaultValues }) => {
isEditing={true}
required={true}
objectData={objectData}
labelWidth={120}
/>
)
},
@ -39,6 +40,7 @@ const NewDocumentSize = ({ onOk, defaultValues }) => {
createdAt: false,
updatedAt: false
}}
labelWidth={120}
isEditing={false}
objectData={objectData}
/>

View File

@ -80,6 +80,7 @@ const DocumentTemplates = () => {
type='documentTemplate'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -21,6 +21,7 @@ const NewDocumentTemplate = ({ onOk, defaultValues }) => {
bordered={false}
isEditing={true}
required={true}
labelWidth={130}
objectData={objectData}
/>
)

View File

@ -84,6 +84,7 @@ const FilamentSkus = () => {
type='filamentSku'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -8,7 +8,11 @@ const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
<NewObjectForm
type='filamentSku'
reset={reset}
defaultValues={defaultValues}
defaultValues={{
overrideCost: false,
color: '#ff0000',
...defaultValues
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
@ -19,7 +23,7 @@ const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
<ObjectInfo
type='filamentSku'
column={1}
labelWidth={70}
labelWidth={80}
bordered={false}
isEditing={true}
required={true}
@ -29,27 +33,26 @@ const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
cost: false,
costWithTax: false,
costTaxRate: false,
overrideCost: false,
vendor: false
}}
/>
)
},
{
title: 'Color & Cost',
key: 'colorCost',
title: 'Cost',
key: 'cost',
content: (
<ObjectInfo
type='filamentSku'
column={1}
labelWidth={100}
labelWidth={120}
required={true}
visibleProperties={{
_id: false,
createdAt: false,
updatedAt: false,
barcode: false,
filament: false,
name: false,
description: false
overrideCost: true,
cost: true,
costTaxRate: true,
costWithTax: true
}}
bordered={false}
isEditing={true}
@ -64,7 +67,7 @@ const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
<ObjectInfo
type='filamentSku'
column={1}
labelWidth={100}
labelWidth={110}
visibleProperties={{
barcode: true,
description: true
@ -88,9 +91,9 @@ const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
_id: false,
_reference: false
}}
labelWidth={100}
bordered={false}
isEditing={false}
labelWidth={120}
objectData={objectData}
/>
)

View File

@ -86,6 +86,7 @@ const Filaments = () => {
cards={viewMode === 'cards'}
visibleColumns={columnVisibility}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
<Modal

View File

@ -238,7 +238,7 @@ const FilamentInfo = () => {
}}
reset={newFilamentSkuOpen}
defaultValues={{
filament: filamentId ? { _id: filamentId } : undefined
filament: objectFormState?.objectData || undefined
}}
/>
</Modal>

View File

@ -18,7 +18,33 @@ const NewFilament = ({ onOk }) => {
bordered={false}
isEditing={true}
required={true}
labelWidth={120}
objectData={objectData}
visibleProperties={{
cost: false,
costTaxRate: false,
costWithTax: false
}}
/>
)
},
{
title: 'Cost',
key: 'cost',
content: (
<ObjectInfo
type='filament'
column={1}
bordered={false}
isEditing={true}
required={true}
labelWidth={120}
objectData={objectData}
visibleProperties={{
cost: true,
costTaxRate: true,
costWithTax: true
}}
/>
)
},
@ -32,6 +58,7 @@ const NewFilament = ({ onOk }) => {
bordered={false}
isEditing={true}
required={false}
labelWidth={90}
objectData={objectData}
/>
)
@ -50,6 +77,7 @@ const NewFilament = ({ onOk }) => {
createdAt: false,
updatedAt: false
}}
labelWidth={120}
isEditing={false}
objectData={objectData}
/>

View File

@ -68,6 +68,7 @@ const Files = () => {
type='file'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
</>

View File

@ -85,6 +85,7 @@ const Hosts = () => {
cards={viewMode === 'cards'}
visibleColumns={columnVisibility}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
<Modal

View File

@ -83,6 +83,7 @@ const Materials = () => {
cards={viewMode === 'cards'}
visibleColumns={columnVisibility}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
<Modal

View File

@ -18,6 +18,7 @@ const NewMaterial = ({ onOk }) => {
bordered={false}
isEditing={true}
required={true}
labelWidth={70}
objectData={objectData}
/>
)
@ -32,6 +33,7 @@ const NewMaterial = ({ onOk }) => {
bordered={false}
isEditing={true}
required={false}
labelWidth={62}
objectData={objectData}
/>
)
@ -51,6 +53,7 @@ const NewMaterial = ({ onOk }) => {
updatedAt: false
}}
isEditing={false}
labelWidth={70}
objectData={objectData}
/>
)

View File

@ -80,6 +80,7 @@ const NoteTypes = () => {
type='noteType'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -23,6 +23,7 @@ const NewNoteType = ({ onOk }) => {
bordered={false}
isEditing={true}
required={true}
labelWidth={72}
objectData={objectData}
/>
)
@ -38,6 +39,7 @@ const NewNoteType = ({ onOk }) => {
isEditing={true}
required={false}
objectData={objectData}
labelWidth={65}
/>
)
},
@ -57,6 +59,7 @@ const NewNoteType = ({ onOk }) => {
}}
isEditing={false}
objectData={objectData}
labelWidth={70}
/>
)
}

View File

@ -83,6 +83,7 @@ const PartSkus = () => {
type='partSku'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -85,6 +85,7 @@ const Parts = (filter) => {
cards={viewMode === 'cards'}
filter={filter}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -26,6 +26,7 @@ const NewPart = ({ onOk, defaultValues }) => {
isEditing={true}
required={true}
objectData={objectData}
labelWidth={70}
visibleProperties={{
file: false,
priceMode: false,
@ -51,6 +52,7 @@ const NewPart = ({ onOk, defaultValues }) => {
isEditing={true}
required={true}
objectData={objectData}
labelWidth={120}
visibleProperties={{
priceMode: true,
margin: true,
@ -74,6 +76,7 @@ const NewPart = ({ onOk, defaultValues }) => {
bordered={false}
isEditing={true}
required={false}
labelWidth={50}
objectData={objectData}
/>
)

View File

@ -83,6 +83,7 @@ const ProductCategories = () => {
cards={viewMode === 'cards'}
visibleColumns={columnVisibility}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
<Modal

View File

@ -19,6 +19,7 @@ const NewProductCategory = ({ onOk }) => {
isEditing={true}
required={true}
objectData={objectData}
labelWidth={70}
/>
)
},
@ -38,6 +39,7 @@ const NewProductCategory = ({ onOk }) => {
}}
isEditing={false}
objectData={objectData}
labelWidth={70}
/>
)
}

View File

@ -84,6 +84,7 @@ const ProductSkus = () => {
type='productSku'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -23,7 +23,7 @@ const NewProductSku = ({ onOk, reset, defaultValues }) => {
<ObjectInfo
type='productSku'
column={1}
labelWidth={70}
labelWidth={80}
bordered={false}
isEditing={true}
required={true}

View File

@ -355,6 +355,7 @@ const Products = () => {
type={'product'}
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -79,6 +79,7 @@ const TaxRates = () => {
type='taxRate'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -5,7 +5,10 @@ import WizardView from '../../common/WizardView'
const NewTaxRate = ({ onOk, defaultValues }) => {
return (
<NewObjectForm type={'taxRate'} defaultValues={{ ...defaultValues }}>
<NewObjectForm
type={'taxRate'}
defaultValues={{ active: true, ...defaultValues }}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
@ -19,6 +22,7 @@ const NewTaxRate = ({ onOk, defaultValues }) => {
isEditing={true}
required={true}
objectData={objectData}
labelWidth={100}
/>
)
},
@ -32,6 +36,7 @@ const NewTaxRate = ({ onOk, defaultValues }) => {
bordered={false}
isEditing={true}
required={false}
labelWidth={130}
objectData={objectData}
/>
)
@ -50,6 +55,7 @@ const NewTaxRate = ({ onOk, defaultValues }) => {
createdAt: false,
updatedAt: false
}}
labelWidth={130}
isEditing={false}
objectData={objectData}
/>

View File

@ -79,6 +79,7 @@ const Vendors = () => {
type='vendor'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -21,6 +21,7 @@ const NewVendor = ({ onOk, defaultValues }) => {
bordered={false}
isEditing={true}
required={true}
labelWidth={80}
objectData={objectData}
/>
)
@ -35,6 +36,7 @@ const NewVendor = ({ onOk, defaultValues }) => {
bordered={false}
isEditing={true}
required={false}
labelWidth={85}
objectData={objectData}
/>
)
@ -54,6 +56,7 @@ const NewVendor = ({ onOk, defaultValues }) => {
updatedAt: false
}}
isEditing={false}
labelWidth={80}
objectData={objectData}
/>
)

View File

@ -82,6 +82,7 @@ const GCodeFiles = () => {
cards={viewMode === 'cards'}
visibleColumns={columnVisibility}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -86,6 +86,7 @@ const Jobs = () => {
visibleColumns={columnVisibility}
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -71,6 +71,7 @@ const SubJobs = () => {
visibleColumns={columnVisibility}
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
</>

View File

@ -79,6 +79,7 @@ const Clients = () => {
type='client'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal
@ -101,4 +102,3 @@ const Clients = () => {
}
export default Clients

View File

@ -80,6 +80,7 @@ const Listings = () => {
type='listing'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -80,6 +80,7 @@ const Marketplaces = () => {
type='marketplace'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal

View File

@ -80,6 +80,7 @@ const SalesOrders = () => {
type='salesOrder'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
expandHeight={true}
/>
</Flex>
<Modal
@ -105,4 +106,3 @@ const SalesOrders = () => {
}
export default SalesOrders

View File

@ -152,8 +152,8 @@ const DashboardNavigation = () => {
style={{
fontSize: '46px',
height: '16px',
marginLeft: '15px',
marginRight: '8px'
marginLeft: '13px',
marginRight: '0px'
}}
/>
)}
@ -225,7 +225,7 @@ const DashboardNavigation = () => {
align='center'
style={{ marginTop: '-2px', marginRight: '6px' }}
>
<Space>
<Space style={{ paddingTop: '2px', marginRight: '8px' }}>
<WebAppSwitcher />
<KeyboardShortcut
shortcut='alt+q'

View File

@ -39,7 +39,7 @@ const DashboardWindowButtons = () => {
<div style={{ width: '80px' }} />
) : null
) : (
<div style={{ width: '95px' }}>
<div style={{ width: '95px', marginRight: '2.5px' }}>
<Flex style={{ position: 'relative', zIndex: 9999 }}>
{maximizeButton}
{minimizeButton}

View File

@ -101,7 +101,8 @@ const ObjectTable = forwardRef(
masterFilter = {},
size = 'middle',
onStateChange,
showFilterSidebar = false
showFilterSidebar = false,
expandHeight = false
},
ref
) => {
@ -728,6 +729,30 @@ const ObjectTable = forwardRef(
setTableData(pages.flatMap((page) => page.items))
}, [pages])
useEffect(() => {
if (!expandHeight || cards) return
const findAntTableBody = (element) => {
if (!element) return null
if (element.classList?.contains('ant-table-body')) return element
for (const child of element.children) {
const found = findAntTableBody(child)
if (found) return found
}
return null
}
const root = tableRef.current?.nativeElement ?? tableRef.current
const tableBody = findAntTableBody(root)
if (!tableBody) return
tableBody.style.minHeight = adjustedScrollHeight
return () => {
tableBody.style.minHeight = ''
}
}, [expandHeight, adjustedScrollHeight, cards, loading, tableData])
// Add columns in the order specified by model.columns
model.columns.forEach((colName) => {
const prop = modelProperties.find((p) => p.name === colName)
@ -878,7 +903,10 @@ const ObjectTable = forwardRef(
return (
<Row
gutter={[16, 16]}
style={{ overflowY: 'auto', maxHeight: adjustedScrollHeight }}
style={{
overflowY: 'auto',
maxHeight: adjustedScrollHeight
}}
ref={cardsContainerRef}
>
{tableData.map((record) => {
@ -1002,7 +1030,8 @@ ObjectTable.propTypes = {
masterFilter: PropTypes.object,
size: PropTypes.string,
onStateChange: PropTypes.func,
showFilterSidebar: PropTypes.bool
showFilterSidebar: PropTypes.bool,
expandHeight: PropTypes.bool
}
export default ObjectTable

View File

@ -1,10 +1,11 @@
import { useContext } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import { Button, Tooltip } from 'antd'
import { Button } from 'antd'
import { ElectronContext } from '../context/ElectronContext'
import OpenInAppIcon from '../../Icons/OpenInAppIcon'
import OpenInBrowserIcon from '../../Icons/OpenInBrowserIcon'
import config from '../../../config'
import KeyboardShortcut from './KeyboardShortcut'
const WebAppSwitcher = () => {
const { isElectron, openExternalUrl } = useContext(ElectronContext)
@ -42,17 +43,14 @@ const WebAppSwitcher = () => {
}
return (
<Tooltip
title={isElectron ? 'Open in browser' : 'Open in app'}
arrow={false}
>
<KeyboardShortcut shortcut='alt+w' hint='ALT W' onTrigger={handleClick}>
<Button
icon={isElectron ? <OpenInBrowserIcon /> : <OpenInAppIcon />}
type='text'
style={{ marginTop: '4px' }}
onClick={handleClick}
/>
</Tooltip>
</KeyboardShortcut>
)
}

View File

@ -30,9 +30,11 @@ import IdDisplay from '../common/IdDisplay'
import config from '../../../config'
import {
getModelByName,
getModelByPrefix
getModelByPrefix,
searchModelsByLabel
} from '../../../database/ObjectModels'
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
import MenuIcon from '../../Icons/MenuIcon'
import { ApiServerContext } from './ApiServerContext'
import { AuthContext } from './AuthContext'
import { ElectronContext } from './ElectronContext'
@ -128,6 +130,22 @@ const SpotlightContent = ({
}
setLoading(false)
const models = searchModelsByLabel(searchQuery.trim()).map((model) => ({
type: model.name,
name: model.labelPlural,
icon: model.icon,
actions: [
{
default: true,
row: true,
icon: MenuIcon,
url: () => {
return model.url
}
}
]
}))
// If the query contains a prefix mode character, and the response is an object, wrap it in an array
if (
/[:?^]/.test(searchQuery) &&
@ -135,9 +153,9 @@ const SpotlightContent = ({
!Array.isArray(data) &&
typeof data === 'object'
) {
setListData([data])
setListData([...models, data])
} else {
setListData(data)
setListData([...models, ...data])
}
// Check if there's a pending query after this fetch completes
@ -290,7 +308,11 @@ const SpotlightContent = ({
const item = listData[0]
let type = item.type || item.objectType || inputPrefix?.type
const model = getModelByName(type)
const defaultAction = model ? getDefaultRowAction(model) : null
const defaultAction = item?.type
? getDefaultRowAction(item)
: model
? getDefaultRowAction(model)
: null
if (defaultAction) {
triggerRowAction(defaultAction, item)
}
@ -304,7 +326,11 @@ const SpotlightContent = ({
const item = listData[index]
let type = item.type || item.objectType || inputPrefix?.type
const model = getModelByName(type)
const defaultAction = model ? getDefaultRowAction(model) : null
const defaultAction = item?.type
? getDefaultRowAction(item)
: model
? getDefaultRowAction(model)
: null
if (defaultAction) {
triggerRowAction(defaultAction, item)
}
@ -447,10 +473,12 @@ const SpotlightContent = ({
<List
dataSource={listData}
renderItem={(item, index) => {
let type = item.objectType || inputPrefix?.type
let type = item.objectType || inputPrefix?.type || item?.type
const model = getModelByName(type)
const Icon = model?.icon
const rowActions = getRowActions(model)
const rowActions = item?.type
? item.actions
: getRowActions(model)
let shortcutText = ''
if (index === 0) {
shortcutText = 'ENTER'
@ -492,7 +520,9 @@ const SpotlightContent = ({
showId={false}
/>
) : null}
{item?._id ? (
<IdDisplay id={item._id} type={type} longId={false} />
) : null}
</Flex>
<Flex gap={'small'}>
{rowActions

View File

@ -213,6 +213,14 @@ export function getModelByPrefix(prefix) {
)
}
export function searchModelsByLabel(label) {
return objectModels.filter(
(meta) =>
meta.label.toLowerCase().includes(label.toLowerCase()) ||
meta.labelPlural.toLowerCase().includes(label.toLowerCase())
)
}
// Utility function to get nested object values
export const getPropertyValue = (obj, path) => {
if (!obj || !path) return undefined

View File

@ -8,6 +8,8 @@ import LockIcon from '../../components/Icons/LockIcon'
export const AppPassword = {
name: 'appPassword',
label: 'App Password',
labelPlural: 'App Passwords',
url: '/dashboard/management/apppasswords',
prefix: 'APP',
icon: AppPasswordIcon,
actions: [

View File

@ -3,6 +3,8 @@ import AuditLogIcon from '../../components/Icons/AuditLogIcon'
export const AuditLog = {
name: 'auditLog',
label: 'Audit Log',
labelPlural: 'Audit Logs',
url: '/dashboard/management/auditlogs',
prefix: 'ADL',
icon: AuditLogIcon,
actions: [],

View File

@ -8,6 +8,8 @@ import BinIcon from '../../components/Icons/BinIcon'
export const Client = {
name: 'client',
label: 'Client',
labelPlural: 'Clients',
url: '/dashboard/sales/clients',
prefix: 'CLI',
icon: ClientIcon,
actions: [

View File

@ -8,6 +8,8 @@ import BinIcon from '../../components/Icons/BinIcon'
export const Courier = {
name: 'courier',
label: 'Courier',
labelPlural: 'Couriers',
url: '/dashboard/management/couriers',
prefix: 'COR',
icon: CourierIcon,
actions: [

View File

@ -8,6 +8,8 @@ import BinIcon from '../../components/Icons/BinIcon'
export const CourierService = {
name: 'courierService',
label: 'Courier Service',
labelPlural: 'Courier Services',
url: '/dashboard/management/courierservices',
prefix: 'COS',
icon: CourierServiceIcon,
actions: [

View File

@ -8,6 +8,8 @@ import dayjs from 'dayjs'
export const DocumentJob = {
name: 'documentJob',
label: 'Document Job',
labelPlural: 'Document Jobs',
url: '/dashboard/management/documentjobs',
prefix: 'DJB',
icon: DocumentJobIcon,
actions: [

View File

@ -7,6 +7,8 @@ import XMarkIcon from '../../components/Icons/XMarkIcon'
export const DocumentPrinter = {
name: 'documentPrinter',
label: 'Document Printer',
labelPlural: 'Document Printers',
url: '/dashboard/management/documentprinters',
prefix: 'DPR',
icon: DocumentPrinterIcon,
actions: [

View File

@ -7,6 +7,8 @@ import DocumentSizeIcon from '../../components/Icons/DocumentSizeIcon'
export const DocumentSize = {
name: 'documentSize',
label: 'Document Size',
labelPlural: 'Document Sizes',
url: '/dashboard/management/documentsizes',
prefix: 'DSZ',
icon: DocumentSizeIcon,
actions: [

View File

@ -8,6 +8,8 @@ import DocumentTemplateIcon from '../../components/Icons/DocumentTemplateIcon'
export const DocumentTemplate = {
name: 'documentTemplate',
label: 'Document Template',
labelPlural: 'Document Templates',
url: '/dashboard/management/documenttemplates',
prefix: 'DTP',
icon: DocumentTemplateIcon,
actions: [

View File

@ -9,6 +9,8 @@ import BinIcon from '../../components/Icons/BinIcon'
export const Filament = {
name: 'filament',
label: 'Filament',
labelPlural: 'Filaments',
url: '/dashboard/management/filaments',
prefix: 'FIL',
icon: FilamentIcon,
actions: [
@ -88,7 +90,14 @@ export const Filament = {
'updatedAt'
],
filters: ['_id', 'name', 'material', 'cost', 'costWithTax'],
sorters: ['name', 'createdAt', 'material', 'cost', 'costWithTax', 'updatedAt'],
sorters: [
'name',
'createdAt',
'material',
'cost',
'costWithTax',
'updatedAt'
],
group: ['diameter', 'material'],
properties: [
{
@ -168,7 +177,7 @@ export const Filament = {
{
name: 'cost',
label: 'Cost',
required: false,
required: true,
columnWidth: 100,
type: 'number',
prefix: '£',
@ -178,7 +187,7 @@ export const Filament = {
{
name: 'costWithTax',
label: 'Cost w/ Tax',
required: false,
required: true,
readOnly: true,
type: 'number',
prefix: '£',
@ -201,7 +210,7 @@ export const Filament = {
{
name: 'costTaxRate',
label: 'Cost Tax Rate',
required: false,
required: true,
type: 'object',
objectType: 'taxRate',
showHyperlink: true,

View File

@ -8,6 +8,8 @@ import BinIcon from '../../components/Icons/BinIcon'
export const FilamentSku = {
name: 'filamentSku',
label: 'Filament SKU',
labelPlural: 'Filament SKUs',
url: '/dashboard/management/filamentskus',
prefix: 'FSU',
icon: FilamentSkuIcon,
actions: [
@ -61,7 +63,6 @@ export const FilamentSku = {
`/dashboard/management/filamentskus/info?filamentSkuId=${_id}&action=delete`
}
],
url: (id) => `/dashboard/management/filamentskus/info?filamentSkuId=${id}`,
columns: [
'_reference',
'name',
@ -170,36 +171,42 @@ export const FilamentSku = {
{
name: 'overrideCost',
label: 'Override Cost',
required: false,
required: true,
type: 'bool',
value: (objectData) => objectData?.overrideCost ?? false,
columnWidth: 150
},
{
name: 'cost',
label: 'Cost',
required: false,
required: true,
type: 'number',
prefix: '£',
min: 0,
step: 0.01,
disabled: (objectData) => !objectData?.overrideCost,
visible: (objectData) => {
return objectData?.overrideCost
},
value: (objectData) =>
objectData?.overrideCost ? objectData?.cost : undefined,
objectData?.overrideCost
? objectData?.cost
: objectData?.filament?.cost,
columnWidth: 100
},
{
name: 'costWithTax',
label: 'Cost w/ Tax',
required: false,
required: true,
readOnly: true,
type: 'number',
prefix: '£',
min: 0,
step: 0.01,
disabled: (objectData) => !objectData?.overrideCost,
visible: (objectData) => {
return objectData?.overrideCost
},
value: (objectData) => {
if (!objectData?.overrideCost) return undefined
if (!objectData?.overrideCost)
return objectData?.filament?.costWithTax || undefined
const cost = objectData?.cost
const taxRate = objectData?.costTaxRate
if (!cost) return 0
@ -215,13 +222,17 @@ export const FilamentSku = {
{
name: 'costTaxRate',
label: 'Cost Tax Rate',
required: false,
required: true,
type: 'object',
objectType: 'taxRate',
showHyperlink: true,
disabled: (objectData) => !objectData?.overrideCost,
visible: (objectData) => {
return objectData?.overrideCost
},
value: (objectData) =>
objectData?.overrideCost ? objectData?.costTaxRate : undefined,
objectData?.overrideCost
? objectData?.costTaxRate
: objectData?.filament?.costTaxRate,
columnWidth: 150
}
]

View File

@ -4,6 +4,8 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const FilamentStock = {
name: 'filamentStock',
label: 'Filament Stock',
labelPlural: 'Filament Stocks',
url: '/dashboard/inventory/filamentstocks',
prefix: 'FLS',
icon: FilamentStockIcon,
actions: [

View File

@ -9,6 +9,8 @@ import BinIcon from '../../components/Icons/BinIcon'
export const File = {
name: 'file',
label: 'File',
labelPlural: 'Files',
url: '/dashboard/management/files',
prefix: 'FLE',
icon: FileIcon,
actions: [
@ -69,7 +71,6 @@ export const File = {
`/dashboard/management/files/info?fileId=${_id}&action=delete`
}
],
url: (id) => `/dashboard/management/files/info?fileId=${id}`,
columns: [
'_reference',
'name',

View File

@ -9,6 +9,8 @@ import EyeIcon from '../../components/Icons/EyeIcon'
export const GCodeFile = {
name: 'gcodeFile',
label: 'GCode File',
labelPlural: 'GCode Files',
url: '/dashboard/production/gcodefiles',
prefix: 'GCF',
icon: GCodeFileIcon,
actions: [

View File

@ -9,6 +9,8 @@ import OTPIcon from '../../components/Icons/OTPIcon'
export const Host = {
name: 'host',
label: 'Host',
labelPlural: 'Hosts',
url: '/dashboard/management/hosts',
prefix: 'HST',
icon: HostIcon,
actions: [

View File

@ -4,6 +4,7 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const Initial = {
name: 'initial',
label: 'Initial',
labelPlural: 'Initials',
prefix: 'INT',
icon: QuestionCircleIcon,
actions: [
@ -16,5 +17,4 @@ export const Initial = {
url: (_id) => `/dashboard/management/initials/info?initialId=${_id}`
}
],
url: () => `#`
}

View File

@ -9,6 +9,8 @@ import PlusIcon from '../../components/Icons/PlusIcon'
export const Invoice = {
name: 'invoice',
label: 'Invoice',
labelPlural: 'Invoices',
url: '/dashboard/finance/invoices',
prefix: 'INV',
icon: InvoiceIcon,
actions: [

View File

@ -6,6 +6,8 @@ import dayjs from 'dayjs'
export const Job = {
name: 'job',
label: 'Job',
labelPlural: 'Jobs',
url: '/dashboard/production/jobs',
prefix: 'JOB',
icon: JobIcon,
actions: [

View File

@ -9,6 +9,8 @@ import PlusIcon from '../../components/Icons/PlusIcon'
export const Listing = {
name: 'listing',
label: 'Listing',
labelPlural: 'Listings',
url: '/dashboard/sales/listings',
prefix: 'LST',
icon: ListingIcon,
actions: [

View File

@ -8,6 +8,7 @@ import BinIcon from '../../components/Icons/BinIcon'
export const ListingVarient = {
name: 'listingVarient',
label: 'Listing Varient',
labelPlural: 'Listing Varients',
prefix: 'LVR',
icon: ListingVarientIcon,
actions: [

View File

@ -10,6 +10,8 @@ import BinIcon from '../../components/Icons/BinIcon'
export const Marketplace = {
name: 'marketplace',
label: 'Marketplace',
labelPlural: 'Marketplaces',
url: '/dashboard/sales/marketplaces',
prefix: 'MKT',
icon: MarketplaceIcon,
actions: [

View File

@ -8,6 +8,8 @@ import BinIcon from '../../components/Icons/BinIcon'
export const Material = {
name: 'material',
label: 'Material',
labelPlural: 'Materials',
url: '/dashboard/management/materials',
prefix: 'MAT',
icon: MaterialIcon,
actions: [
@ -60,7 +62,6 @@ export const Material = {
`/dashboard/management/materials/info?materialId=${_id}&action=delete`
}
],
url: (id) => `/dashboard/management/materials/info?materialId=${id}`,
columns: ['_reference', 'name', 'tags', 'createdAt', 'updatedAt'],
filters: ['_id', 'name', 'tags'],
sorters: ['name', 'createdAt', 'updatedAt', '_id'],

View File

@ -4,6 +4,7 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const Note = {
name: 'note',
label: 'Note',
labelPlural: 'Notes',
prefix: 'NTE',
icon: NoteIcon,
actions: [

View File

@ -7,6 +7,8 @@ import XMarkIcon from '../../components/Icons/XMarkIcon'
export const NoteType = {
name: 'noteType',
label: 'Note Type',
labelPlural: 'Note Types',
url: '/dashboard/management/notetypes',
prefix: 'NTY',
icon: NoteTypeIcon,
actions: [

View File

@ -8,6 +8,8 @@ import XMarkIcon from '../../components/Icons/XMarkIcon'
export const OrderItem = {
name: 'orderItem',
label: 'Order Item',
labelPlural: 'Order Items',
url: '/dashboard/inventory/orderitems',
prefix: 'ODI',
icon: OrderItemIcon,
actions: [

View File

@ -8,6 +8,8 @@ import PlusIcon from '../../components/Icons/PlusIcon'
export const Part = {
name: 'part',
label: 'Part',
labelPlural: 'Parts',
url: '/dashboard/management/parts',
prefix: 'PRT',
icon: PartIcon,
actions: [
@ -74,8 +76,26 @@ export const Part = {
'createdAt',
'updatedAt'
],
filters: ['name', '_id', 'priceMode', 'cost', 'costWithTax', 'price', 'priceWithTax'],
sorters: ['name', 'priceMode', 'cost', 'costWithTax', 'price', 'priceWithTax', 'createdAt', 'updatedAt', '_id'],
filters: [
'name',
'_id',
'priceMode',
'cost',
'costWithTax',
'price',
'priceWithTax'
],
sorters: [
'name',
'priceMode',
'cost',
'costWithTax',
'price',
'priceWithTax',
'createdAt',
'updatedAt',
'_id'
],
properties: [
{
name: '_id',
@ -119,13 +139,6 @@ export const Part = {
readOnly: true,
columnWidth: 175
},
{
name: 'fileName',
label: 'File Name',
required: false,
type: 'text',
columnWidth: 200
},
{
name: 'file',
label: 'File',
@ -137,7 +150,7 @@ export const Part = {
{
name: 'cost',
label: 'Cost',
required: false,
required: true,
type: 'number',
prefix: '£',
min: 0,
@ -147,7 +160,7 @@ export const Part = {
{
name: 'costWithTax',
label: 'Cost w/ Tax',
required: false,
required: true,
readOnly: true,
type: 'number',
prefix: '£',
@ -170,7 +183,7 @@ export const Part = {
{
name: 'costTaxRate',
label: 'Cost Tax Rate',
required: false,
required: true,
type: 'object',
objectType: 'taxRate',
showHyperlink: true,
@ -179,14 +192,14 @@ export const Part = {
{
name: 'priceMode',
label: 'Price Mode',
required: false,
required: true,
columnWidth: 150,
type: 'priceMode'
},
{
name: 'price',
label: 'Price',
required: false,
required: true,
type: 'number',
prefix: '£',
min: 0,
@ -211,19 +224,31 @@ export const Part = {
{
name: 'margin',
label: 'Margin',
required: false,
required: true,
type: 'number',
disabled: (objectData) => objectData?.priceMode == 'amount',
readOnly: (objectData) => objectData?.priceMode == 'amount',
suffix: '%',
min: 0,
max: 100,
step: 0.01,
value: (objectData) => {
const priceMode = objectData?.priceMode
const cost = objectData?.cost
if (priceMode == 'amount') {
const price = objectData?.price
if (price != null && cost != null) {
return Number(((price / cost - 1) * 100).toFixed(2)) || undefined
}
return undefined
}
return objectData?.margin
},
columnWidth: 85
},
{
name: 'priceWithTax',
label: 'Price w/ Tax',
required: false,
required: true,
readOnly: true,
type: 'number',
prefix: '£',
@ -255,7 +280,7 @@ export const Part = {
{
name: 'priceTaxRate',
label: 'Price Tax Rate',
required: false,
required: true,
type: 'object',
objectType: 'taxRate',
showHyperlink: true,

View File

@ -8,6 +8,8 @@ import BinIcon from '../../components/Icons/BinIcon'
export const PartSku = {
name: 'partSku',
label: 'Part SKU',
labelPlural: 'Part SKUs',
url: '/dashboard/management/partskus',
prefix: 'PSU',
icon: PartSkuIcon,
actions: [
@ -60,7 +62,6 @@ export const PartSku = {
`/dashboard/management/partskus/info?partSkuId=${_id}&action=delete`
}
],
url: (id) => `/dashboard/management/partskus/info?partSkuId=${id}`,
columns: [
'_reference',
'name',

View File

@ -4,6 +4,8 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
export const PartStock = {
name: 'partStock',
label: 'Part Stock',
labelPlural: 'Part Stocks',
url: '/dashboard/inventory/partstocks',
prefix: 'PTS',
icon: PartStockIcon,
actions: [
@ -16,7 +18,6 @@ export const PartStock = {
url: (_id) => `/dashboard/inventory/partstocks/info?partStockId=${_id}`
}
],
url: (id) => `/dashboard/inventory/partstocks/info?partStockId=${id}`,
filters: ['_id', 'partSku', 'startingQuantity', 'currentQuantity'],
sorters: ['partSku', 'startingQuantity', 'currentQuantity'],
columns: [

View File

@ -8,6 +8,8 @@ import BinIcon from '../../components/Icons/BinIcon'
export const Payment = {
name: 'payment',
label: 'Payment',
labelPlural: 'Payments',
url: '/dashboard/finance/payments',
prefix: 'PAY',
icon: PaymentIcon,
actions: [

View File

@ -14,6 +14,8 @@ import JobIcon from '../../components/Icons/JobIcon'
export const Printer = {
name: 'printer',
label: 'Printer',
labelPlural: 'Printers',
url: '/dashboard/production/printers',
prefix: 'PRN',
icon: PrinterIcon,
actions: [

View File

@ -8,6 +8,8 @@ import PlusIcon from '../../components/Icons/PlusIcon'
export const Product = {
name: 'product',
label: 'Product',
labelPlural: 'Products',
url: '/dashboard/management/products',
prefix: 'PRD',
icon: ProductIcon,
actions: [
@ -254,11 +256,23 @@ export const Product = {
label: 'Margin',
required: true,
type: 'number',
disabled: (objectData) => objectData?.priceMode == 'amount',
readOnly: (objectData) => objectData?.priceMode == 'amount',
suffix: '%',
min: 0,
max: 100,
step: 0.01,
value: (objectData) => {
const priceMode = objectData?.priceMode
const cost = objectData?.cost
if (priceMode == 'amount') {
const price = objectData?.price
if (price != null && cost != null) {
return Number(((price / cost - 1) * 100).toFixed(2)) || undefined
}
return undefined
}
return objectData?.margin
},
columnWidth: 85
},
{

View File

@ -8,6 +8,8 @@ import BinIcon from '../../components/Icons/BinIcon'
export const ProductCategory = {
name: 'productCategory',
label: 'Product Category',
labelPlural: 'Product Categories',
url: '/dashboard/management/productcategories',
prefix: 'PCG',
endpoint: 'productcategories',
icon: ProductCategoryIcon,
@ -62,8 +64,6 @@ export const ProductCategory = {
`/dashboard/management/productcategories/info?productCategoryId=${_id}&action=delete`
}
],
url: (id) =>
`/dashboard/management/productcategories/info?productCategoryId=${id}`,
columns: ['_reference', 'name', 'createdAt', 'updatedAt'],
filters: ['_id', 'name'],
sorters: ['name', 'createdAt', 'updatedAt', '_id'],

View File

@ -8,6 +8,8 @@ import BinIcon from '../../components/Icons/BinIcon'
export const ProductSku = {
name: 'productSku',
label: 'Product SKU',
labelPlural: 'Product SKUs',
url: '/dashboard/management/productskus',
prefix: 'SKU',
icon: ProductSkuIcon,
actions: [
@ -60,7 +62,6 @@ export const ProductSku = {
`/dashboard/management/productskus/info?productSkuId=${_id}&action=delete`
}
],
url: (id) => `/dashboard/management/productskus/info?productSkuId=${id}`,
columns: [
'_reference',
'name',

View File

@ -8,6 +8,8 @@ import BinIcon from '../../components/Icons/BinIcon'
export const ProductStock = {
name: 'productStock',
label: 'Product Stock',
labelPlural: 'Product Stocks',
url: '/dashboard/inventory/productstocks',
prefix: 'PDS',
icon: ProductStockIcon,
actions: [
@ -84,7 +86,6 @@ export const ProductStock = {
}
}
],
url: (id) => `/dashboard/inventory/productstocks/info?productStockId=${id}`,
filters: ['_id', 'productSku', 'currentQuantity'],
sorters: ['productSku', 'currentQuantity'],
columns: [

Some files were not shown because too many files have changed in this diff Show More