Compare commits

..

No commits in common. "main" and "development" have entirely different histories.

113 changed files with 158 additions and 801 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 .", "electron": "cross-env ELECTRON_START_URL=http://0.0.0.0:5780 && cross-env NODE_ENV=development && electron .",
"start": "serve -s build", "start": "serve -s build",
"build": "vite 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 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 cross-env NODE_ENV=development electron public/electron.js\"",
"build:electron": "vite build && electron-builder", "build:electron": "vite build && electron-builder",
"build:cloudflare": "cross-env VITE_DEPLOY_TARGET=cloudflare vite build", "build:cloudflare": "cross-env VITE_DEPLOY_TARGET=cloudflare vite build",
"deploy": "npm run build:cloudflare && wrangler pages deploy --branch main" "deploy": "npm run build:cloudflare && wrangler pages deploy --branch main"
@ -213,7 +213,7 @@
"win": { "win": {
"target": [ "target": [
"nsis", "nsis",
"msiWrapped" "msi"
], ],
"protocols": [ "protocols": [
{ {
@ -232,12 +232,6 @@
"allowToChangeInstallationDirectory": true, "allowToChangeInstallationDirectory": true,
"include": "scripts/installer.nsh", "include": "scripts/installer.nsh",
"perMachine": true "perMachine": true
},
"msiWrapped": {
"upgradeCode": "{735812DB-E33B-57A0-8FBC-5FC3155925AA}",
"perMachine": true,
"impersonate": false,
"wrappedInstallerArgs": "/S"
} }
} }
} }

View File

@ -1,408 +1,82 @@
import { spawn } from 'child_process' import { spawn } from 'child_process'
import { promises as fs } from 'fs'
import os from 'os'
import path from 'path'
import process from 'process' import process from 'process'
const MSI_OLE_HEADER = Buffer.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]) const buildWindowsInstallCommand = (installerPath) => ({
const DEBUG_PREFIX = '[app-update][win-progress]' command: 'cmd.exe',
args: [
'/d',
'/s',
'/c',
`timeout /t 2 /nobreak >NUL && msiexec.exe /i "${installerPath}" /qn /norestart`
]
})
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) export const launchWindowsInstaller = (
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, app,
installerPath, installerPath,
webContents, webContents,
{ sendProgress, getInstallErrorMessage } { sendProgress, getInstallErrorMessage }
) => { ) => {
const resolvedPath = await prepareInstallerPath(installerPath) const { command, args } = buildWindowsInstallCommand(installerPath)
const logPath = path.join(path.dirname(resolvedPath), 'install.log')
debugLog('prepared installer', { console.log('[app-update] launching installer:', {
installerPath, installerPath,
resolvedPath, command,
logPath args,
shellCommand: args.join(' '),
platform: process.platform
}) })
sendProgress(webContents, { sendProgress(webContents, {
phase: 'installing', phase: 'installing',
percent: 0, percent: 100,
message: 'Installing update...' message: 'Installing update. Farm Control will restart automatically.'
}) })
await fs.unlink(logPath).catch(() => {})
// 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) => { return new Promise((resolve, reject) => {
let processOutput = '' let installerOutput = ''
const startedAt = Date.now()
const installerArgs = [ const installerProcess = spawn(command, args, {
'/i', detached: true,
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'], stdio: ['ignore', 'pipe', 'pipe'],
windowsHide: true windowsHide: true
}) })
installerProcess.stdout?.on('data', (data) => { installerProcess.stdout?.on('data', (data) => {
const text = data.toString('utf16le') const text = data.toString()
processOutput += text installerOutput += text
debugLog('msiexec stdout chunk', { console.log('[app-update] installer stdout:', text)
length: text.length,
preview: text.slice(0, 200)
})
}) })
installerProcess.stderr?.on('data', (data) => { installerProcess.stderr?.on('data', (data) => {
const text = data.toString('utf16le') const text = data.toString()
processOutput += text installerOutput += text
debugLog('msiexec stderr chunk', { console.error('[app-update] installer stderr:', text)
length: text.length,
preview: text.slice(0, 200)
})
}) })
installerProcess.on('spawn', () => { installerProcess.on('spawn', () => {
debugLog('msiexec spawned', { console.log('[app-update] installer spawned, pid:', installerProcess.pid)
pid: installerProcess.pid,
elapsedMs: Date.now() - startedAt
})
}) })
installerProcess.on('error', async (error) => { installerProcess.on('error', (error) => {
console.error(`${DEBUG_PREFIX} installer spawn error:`, error) console.error('[app-update] 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, { sendProgress(webContents, {
phase: 'error', phase: 'error',
percent: null, percent: null,
message message: error?.message || 'Failed to start update installer.'
}) })
reject(error) reject(error)
}) })
installerProcess.on('exit', async (code, signal) => { installerProcess.on('exit', (code, signal) => {
const watchedOutput = await stopProgressWatch() console.log('[app-update] installer exited:', {
const output = watchedOutput || processOutput
const finalParse = parseWindowsInstallerProgress(output)
debugLog('msiexec exited', {
code, code,
signal, signal,
elapsedMs: Date.now() - startedAt, output: installerOutput
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) { if (code !== 0) {
const message = getInstallErrorMessage(null, output) const message = getInstallErrorMessage(null, installerOutput)
sendProgress(webContents, { sendProgress(webContents, {
phase: 'error', phase: 'error',
percent: null, percent: null,
@ -412,38 +86,14 @@ export const launchWindowsInstaller = async (
return return
} }
const succeeded =
isWindowsInstallSuccessful(output) ||
(code === 0 && !isWindowsInstallFailed(output))
debugLog('install success evaluation', {
succeeded,
isSuccessful: isWindowsInstallSuccessful(output),
isFailed: isWindowsInstallFailed(output),
exitCode: code
})
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, { sendProgress(webContents, {
phase: 'installing', phase: 'installing',
percent: percent ?? 100, percent: 100,
message: message || 'Installation complete. Restarting Farm Control...' message: 'Installation complete. Restarting Farm Control...'
}) })
debugLog('installer completed successfully')
resolve() resolve()
}) })
installerProcess.unref()
}) })
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,33 +18,7 @@ const NewFilament = ({ onOk }) => {
bordered={false} bordered={false}
isEditing={true} isEditing={true}
required={true} required={true}
labelWidth={120}
objectData={objectData} 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
}}
/> />
) )
}, },
@ -58,7 +32,6 @@ const NewFilament = ({ onOk }) => {
bordered={false} bordered={false}
isEditing={true} isEditing={true}
required={false} required={false}
labelWidth={90}
objectData={objectData} objectData={objectData}
/> />
) )
@ -77,7 +50,6 @@ const NewFilament = ({ onOk }) => {
createdAt: false, createdAt: false,
updatedAt: false updatedAt: false
}} }}
labelWidth={120}
isEditing={false} isEditing={false}
objectData={objectData} objectData={objectData}
/> />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -101,8 +101,7 @@ const ObjectTable = forwardRef(
masterFilter = {}, masterFilter = {},
size = 'middle', size = 'middle',
onStateChange, onStateChange,
showFilterSidebar = false, showFilterSidebar = false
expandHeight = false
}, },
ref ref
) => { ) => {
@ -729,30 +728,6 @@ const ObjectTable = forwardRef(
setTableData(pages.flatMap((page) => page.items)) setTableData(pages.flatMap((page) => page.items))
}, [pages]) }, [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 // Add columns in the order specified by model.columns
model.columns.forEach((colName) => { model.columns.forEach((colName) => {
const prop = modelProperties.find((p) => p.name === colName) const prop = modelProperties.find((p) => p.name === colName)
@ -903,10 +878,7 @@ const ObjectTable = forwardRef(
return ( return (
<Row <Row
gutter={[16, 16]} gutter={[16, 16]}
style={{ style={{ overflowY: 'auto', maxHeight: adjustedScrollHeight }}
overflowY: 'auto',
maxHeight: adjustedScrollHeight
}}
ref={cardsContainerRef} ref={cardsContainerRef}
> >
{tableData.map((record) => { {tableData.map((record) => {
@ -1030,8 +1002,7 @@ ObjectTable.propTypes = {
masterFilter: PropTypes.object, masterFilter: PropTypes.object,
size: PropTypes.string, size: PropTypes.string,
onStateChange: PropTypes.func, onStateChange: PropTypes.func,
showFilterSidebar: PropTypes.bool, showFilterSidebar: PropTypes.bool
expandHeight: PropTypes.bool
} }
export default ObjectTable export default ObjectTable

View File

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

View File

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

View File

@ -213,14 +213,6 @@ 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 // Utility function to get nested object values
export const getPropertyValue = (obj, path) => { export const getPropertyValue = (obj, path) => {
if (!obj || !path) return undefined if (!obj || !path) return undefined

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,8 +8,6 @@ import PlusIcon from '../../components/Icons/PlusIcon'
export const Product = { export const Product = {
name: 'product', name: 'product',
label: 'Product', label: 'Product',
labelPlural: 'Products',
url: '/dashboard/management/products',
prefix: 'PRD', prefix: 'PRD',
icon: ProductIcon, icon: ProductIcon,
actions: [ actions: [
@ -256,23 +254,11 @@ export const Product = {
label: 'Margin', label: 'Margin',
required: true, required: true,
type: 'number', type: 'number',
readOnly: (objectData) => objectData?.priceMode == 'amount', disabled: (objectData) => objectData?.priceMode == 'amount',
suffix: '%', suffix: '%',
min: 0, min: 0,
max: 100, max: 100,
step: 0.01, 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 columnWidth: 85
}, },
{ {

View File

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

View File

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

View File

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

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