Compare commits
No commits in common. "main" and "development" have entirely different histories.
main
...
developmen
10
package.json
10
package.json
@ -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 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: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",
|
||||
"msiWrapped"
|
||||
"msi"
|
||||
],
|
||||
"protocols": [
|
||||
{
|
||||
@ -232,12 +232,6 @@
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"include": "scripts/installer.nsh",
|
||||
"perMachine": true
|
||||
},
|
||||
"msiWrapped": {
|
||||
"upgradeCode": "{735812DB-E33B-57A0-8FBC-5FC3155925AA}",
|
||||
"perMachine": true,
|
||||
"impersonate": false,
|
||||
"wrappedInstallerArgs": "/S"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,408 +1,82 @@
|
||||
import { spawn } from 'child_process'
|
||||
import { promises as fs } from 'fs'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import process from 'process'
|
||||
|
||||
const MSI_OLE_HEADER = Buffer.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1])
|
||||
const DEBUG_PREFIX = '[app-update][win-progress]'
|
||||
const buildWindowsInstallCommand = (installerPath) => ({
|
||||
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))
|
||||
|
||||
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 (
|
||||
export const launchWindowsInstaller = (
|
||||
app,
|
||||
installerPath,
|
||||
webContents,
|
||||
{ sendProgress, getInstallErrorMessage }
|
||||
) => {
|
||||
const resolvedPath = await prepareInstallerPath(installerPath)
|
||||
const logPath = path.join(path.dirname(resolvedPath), 'install.log')
|
||||
const { command, args } = buildWindowsInstallCommand(installerPath)
|
||||
|
||||
debugLog('prepared installer', {
|
||||
console.log('[app-update] launching installer:', {
|
||||
installerPath,
|
||||
resolvedPath,
|
||||
logPath
|
||||
command,
|
||||
args,
|
||||
shellCommand: args.join(' '),
|
||||
platform: process.platform
|
||||
})
|
||||
|
||||
sendProgress(webContents, {
|
||||
phase: 'installing',
|
||||
percent: 0,
|
||||
message: 'Installing update...'
|
||||
percent: 100,
|
||||
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) => {
|
||||
let processOutput = ''
|
||||
const startedAt = Date.now()
|
||||
let installerOutput = ''
|
||||
|
||||
const installerArgs = [
|
||||
'/i',
|
||||
resolvedPath,
|
||||
'/qn',
|
||||
'/norestart',
|
||||
'/L*v!',
|
||||
logPath
|
||||
]
|
||||
|
||||
debugLog('spawning msiexec', {
|
||||
args: installerArgs,
|
||||
elapsedMs: Date.now() - startedAt
|
||||
})
|
||||
|
||||
const installerProcess = spawn('msiexec.exe', installerArgs, {
|
||||
const installerProcess = spawn(command, args, {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
windowsHide: true
|
||||
})
|
||||
|
||||
installerProcess.stdout?.on('data', (data) => {
|
||||
const text = data.toString('utf16le')
|
||||
processOutput += text
|
||||
debugLog('msiexec stdout chunk', {
|
||||
length: text.length,
|
||||
preview: text.slice(0, 200)
|
||||
})
|
||||
const text = data.toString()
|
||||
installerOutput += text
|
||||
console.log('[app-update] installer stdout:', text)
|
||||
})
|
||||
|
||||
installerProcess.stderr?.on('data', (data) => {
|
||||
const text = data.toString('utf16le')
|
||||
processOutput += text
|
||||
debugLog('msiexec stderr chunk', {
|
||||
length: text.length,
|
||||
preview: text.slice(0, 200)
|
||||
})
|
||||
const text = data.toString()
|
||||
installerOutput += text
|
||||
console.error('[app-update] installer stderr:', text)
|
||||
})
|
||||
|
||||
installerProcess.on('spawn', () => {
|
||||
debugLog('msiexec spawned', {
|
||||
pid: installerProcess.pid,
|
||||
elapsedMs: Date.now() - startedAt
|
||||
})
|
||||
console.log('[app-update] installer spawned, pid:', installerProcess.pid)
|
||||
})
|
||||
|
||||
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.'
|
||||
installerProcess.on('error', (error) => {
|
||||
console.error('[app-update] installer spawn error:', error)
|
||||
sendProgress(webContents, {
|
||||
phase: 'error',
|
||||
percent: null,
|
||||
message
|
||||
message: error?.message || 'Failed to start update installer.'
|
||||
})
|
||||
reject(error)
|
||||
})
|
||||
|
||||
installerProcess.on('exit', async (code, signal) => {
|
||||
const watchedOutput = await stopProgressWatch()
|
||||
const output = watchedOutput || processOutput
|
||||
const finalParse = parseWindowsInstallerProgress(output)
|
||||
|
||||
debugLog('msiexec exited', {
|
||||
installerProcess.on('exit', (code, signal) => {
|
||||
console.log('[app-update] installer exited:', {
|
||||
code,
|
||||
signal,
|
||||
elapsedMs: Date.now() - startedAt,
|
||||
watchedOutputLength: watchedOutput.length,
|
||||
processOutputLength: processOutput.length,
|
||||
parsed: finalParse.stats,
|
||||
outputPreview: output.slice(0, 500).replace(/\s+/g, ' ')
|
||||
output: installerOutput
|
||||
})
|
||||
|
||||
debugLog('keeping install log', { logPath })
|
||||
|
||||
if (code !== 0) {
|
||||
const message = getInstallErrorMessage(null, output)
|
||||
const message = getInstallErrorMessage(null, installerOutput)
|
||||
sendProgress(webContents, {
|
||||
phase: 'error',
|
||||
percent: null,
|
||||
@ -412,38 +86,14 @@ export const launchWindowsInstaller = async (
|
||||
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, {
|
||||
phase: 'installing',
|
||||
percent: percent ?? 100,
|
||||
message: message || 'Installation complete. Restarting Farm Control...'
|
||||
percent: 100,
|
||||
message: 'Installation complete. Restarting Farm Control...'
|
||||
})
|
||||
|
||||
debugLog('installer completed successfully')
|
||||
resolve()
|
||||
})
|
||||
|
||||
installerProcess.unref()
|
||||
})
|
||||
}
|
||||
|
||||
@ -80,7 +80,6 @@ const Invoices = () => {
|
||||
type='invoice'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -80,7 +80,6 @@ const Payments = () => {
|
||||
type='payment'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
@ -106,3 +105,4 @@ const Payments = () => {
|
||||
}
|
||||
|
||||
export default Payments
|
||||
|
||||
|
||||
@ -80,7 +80,6 @@ const TaxRecords = () => {
|
||||
type='taxRecord'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -86,7 +86,6 @@ const FilamentStocks = () => {
|
||||
type='filamentStock'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -80,7 +80,6 @@ const OrderItems = () => {
|
||||
type='orderItem'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -86,7 +86,6 @@ const PartStocks = () => {
|
||||
type='partStock'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -87,7 +87,6 @@ const ProductStocks = () => {
|
||||
type='productStock'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -80,7 +80,6 @@ const PurchaseOrders = () => {
|
||||
type='purchaseOrder'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -80,7 +80,6 @@ const Shipments = () => {
|
||||
type='shipment'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -86,7 +86,6 @@ const StockAudits = () => {
|
||||
type='stockAudit'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -69,7 +69,6 @@ const StockEvents = () => {
|
||||
type='stockEvent'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
|
||||
@ -84,7 +84,6 @@ const StockLocations = () => {
|
||||
type='stockLocation'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -84,7 +84,6 @@ const StockTransfers = () => {
|
||||
type='stockTransfer'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -80,7 +80,6 @@ const AppPasswords = () => {
|
||||
cards={viewMode === 'cards'}
|
||||
visibleColumns={columnVisibility}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
|
||||
@ -24,12 +24,25 @@ 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',
|
||||
@ -46,7 +59,6 @@ const NewAppPassword = ({ onOk, reset, defaultValues = {} }) => {
|
||||
secret: false
|
||||
}}
|
||||
isEditing={false}
|
||||
labelWidth={70}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -72,17 +72,11 @@ const getDownloadStageStatus = (phase, isError) => {
|
||||
return 'pending'
|
||||
}
|
||||
|
||||
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 isInstallComplete = (phase, message) =>
|
||||
phase === 'installing' &&
|
||||
String(message || '')
|
||||
.toLowerCase()
|
||||
.includes('complete')
|
||||
|
||||
const getInstallStageStatus = (phase, isError, message) => {
|
||||
if (isError && ['downloaded', 'installing'].includes(phase)) return 'error'
|
||||
@ -112,7 +106,7 @@ const UpdateStage = ({ stage, status, percent, detail }) => {
|
||||
const resolvedStatus =
|
||||
status !== 'error' && resolvedPercent === 100 ? 'complete' : status
|
||||
const color = getStageColor(resolvedStatus, token)
|
||||
const showProgress = resolvedStatus === 'active' && stage !== 'restart'
|
||||
const showProgress = resolvedStatus === 'active'
|
||||
|
||||
const StatusIcon =
|
||||
resolvedStatus === 'complete'
|
||||
@ -179,6 +173,8 @@ const AppUpdateProgress = ({ progress, update, onClose }) => {
|
||||
|
||||
const installDetail = installStatus === 'active' ? message : null
|
||||
|
||||
const restartDetail = restartStatus === 'active' ? message : null
|
||||
|
||||
return (
|
||||
<Flex vertical gap='middle'>
|
||||
<Text>
|
||||
@ -201,7 +197,11 @@ const AppUpdateProgress = ({ progress, update, onClose }) => {
|
||||
percent={installPercent}
|
||||
detail={installDetail}
|
||||
/>
|
||||
<UpdateStage stage='restart' status={restartStatus} />
|
||||
<UpdateStage
|
||||
stage='restart'
|
||||
status={restartStatus}
|
||||
detail={restartDetail}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Modal
|
||||
|
||||
@ -62,7 +62,6 @@ const AuditLogs = () => {
|
||||
visibleColumns={columnVisibility}
|
||||
type='auditLog'
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
|
||||
@ -80,7 +80,6 @@ const CourierServices = () => {
|
||||
type='courierService'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -26,7 +26,6 @@ const NewCourierService = ({ onOk, defaultValues }) => {
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
labelWidth={80}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -40,7 +39,6 @@ const NewCourierService = ({ onOk, defaultValues }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
labelWidth={120}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
@ -61,7 +59,6 @@ const NewCourierService = ({ onOk, defaultValues }) => {
|
||||
}}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
labelWidth={120}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -79,7 +79,6 @@ const Couriers = () => {
|
||||
type='courier'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -19,7 +19,6 @@ const NewCourier = ({ onOk, defaultValues }) => {
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
labelWidth={75}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -34,7 +33,6 @@ const NewCourier = ({ onOk, defaultValues }) => {
|
||||
isEditing={true}
|
||||
required={false}
|
||||
objectData={objectData}
|
||||
labelWidth={85}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -54,7 +52,6 @@ const NewCourier = ({ onOk, defaultValues }) => {
|
||||
}}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
labelWidth={85}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -80,7 +80,6 @@ const DocumentJobs = () => {
|
||||
type='documentJob'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -35,7 +35,6 @@ const NewDocumentJob = ({ onOk, defaultValues = {} }) => {
|
||||
column={1}
|
||||
visibleProperties={{ name: false }}
|
||||
bordered={false}
|
||||
labelWidth={115}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
|
||||
@ -79,7 +79,6 @@ const DocumentPrinters = () => {
|
||||
type='documentPrinter'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -78,7 +78,6 @@ const DocumentSizes = () => {
|
||||
type='documentSize'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -22,7 +22,6 @@ const NewDocumentSize = ({ onOk, defaultValues }) => {
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
labelWidth={120}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -40,7 +39,6 @@ const NewDocumentSize = ({ onOk, defaultValues }) => {
|
||||
createdAt: false,
|
||||
updatedAt: false
|
||||
}}
|
||||
labelWidth={120}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
|
||||
@ -80,7 +80,6 @@ const DocumentTemplates = () => {
|
||||
type='documentTemplate'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -21,7 +21,6 @@ const NewDocumentTemplate = ({ onOk, defaultValues }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
labelWidth={130}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -84,7 +84,6 @@ const FilamentSkus = () => {
|
||||
type='filamentSku'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -8,11 +8,7 @@ const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
|
||||
<NewObjectForm
|
||||
type='filamentSku'
|
||||
reset={reset}
|
||||
defaultValues={{
|
||||
overrideCost: false,
|
||||
color: '#ff0000',
|
||||
...defaultValues
|
||||
}}
|
||||
defaultValues={defaultValues}
|
||||
>
|
||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||
const steps = [
|
||||
@ -23,7 +19,7 @@ const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
|
||||
<ObjectInfo
|
||||
type='filamentSku'
|
||||
column={1}
|
||||
labelWidth={80}
|
||||
labelWidth={70}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
@ -33,26 +29,27 @@ const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
|
||||
cost: false,
|
||||
costWithTax: false,
|
||||
costTaxRate: false,
|
||||
overrideCost: false,
|
||||
vendor: false
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Cost',
|
||||
key: 'cost',
|
||||
title: 'Color & Cost',
|
||||
key: 'colorCost',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='filamentSku'
|
||||
column={1}
|
||||
labelWidth={120}
|
||||
required={true}
|
||||
labelWidth={100}
|
||||
visibleProperties={{
|
||||
overrideCost: true,
|
||||
cost: true,
|
||||
costTaxRate: true,
|
||||
costWithTax: true
|
||||
_id: false,
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
barcode: false,
|
||||
filament: false,
|
||||
name: false,
|
||||
description: false
|
||||
}}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
@ -67,7 +64,7 @@ const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
|
||||
<ObjectInfo
|
||||
type='filamentSku'
|
||||
column={1}
|
||||
labelWidth={110}
|
||||
labelWidth={100}
|
||||
visibleProperties={{
|
||||
barcode: true,
|
||||
description: true
|
||||
@ -91,9 +88,9 @@ const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
|
||||
_id: false,
|
||||
_reference: false
|
||||
}}
|
||||
labelWidth={100}
|
||||
bordered={false}
|
||||
isEditing={false}
|
||||
labelWidth={120}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -86,7 +86,6 @@ const Filaments = () => {
|
||||
cards={viewMode === 'cards'}
|
||||
visibleColumns={columnVisibility}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
|
||||
@ -238,7 +238,7 @@ const FilamentInfo = () => {
|
||||
}}
|
||||
reset={newFilamentSkuOpen}
|
||||
defaultValues={{
|
||||
filament: objectFormState?.objectData || undefined
|
||||
filament: filamentId ? { _id: filamentId } : undefined
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
@ -18,33 +18,7 @@ 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
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -58,7 +32,6 @@ const NewFilament = ({ onOk }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
labelWidth={90}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
@ -77,7 +50,6 @@ const NewFilament = ({ onOk }) => {
|
||||
createdAt: false,
|
||||
updatedAt: false
|
||||
}}
|
||||
labelWidth={120}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
|
||||
@ -68,7 +68,6 @@ const Files = () => {
|
||||
type='file'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
|
||||
@ -85,7 +85,6 @@ const Hosts = () => {
|
||||
cards={viewMode === 'cards'}
|
||||
visibleColumns={columnVisibility}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
|
||||
@ -83,7 +83,6 @@ const Materials = () => {
|
||||
cards={viewMode === 'cards'}
|
||||
visibleColumns={columnVisibility}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
|
||||
@ -18,7 +18,6 @@ const NewMaterial = ({ onOk }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
labelWidth={70}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
@ -33,7 +32,6 @@ const NewMaterial = ({ onOk }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
labelWidth={62}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
@ -53,7 +51,6 @@ const NewMaterial = ({ onOk }) => {
|
||||
updatedAt: false
|
||||
}}
|
||||
isEditing={false}
|
||||
labelWidth={70}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -80,7 +80,6 @@ const NoteTypes = () => {
|
||||
type='noteType'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -23,7 +23,6 @@ const NewNoteType = ({ onOk }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
labelWidth={72}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
@ -39,7 +38,6 @@ const NewNoteType = ({ onOk }) => {
|
||||
isEditing={true}
|
||||
required={false}
|
||||
objectData={objectData}
|
||||
labelWidth={65}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -59,7 +57,6 @@ const NewNoteType = ({ onOk }) => {
|
||||
}}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
labelWidth={70}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -83,7 +83,6 @@ const PartSkus = () => {
|
||||
type='partSku'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -85,7 +85,6 @@ const Parts = (filter) => {
|
||||
cards={viewMode === 'cards'}
|
||||
filter={filter}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -26,7 +26,6 @@ const NewPart = ({ onOk, defaultValues }) => {
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
labelWidth={70}
|
||||
visibleProperties={{
|
||||
file: false,
|
||||
priceMode: false,
|
||||
@ -52,7 +51,6 @@ const NewPart = ({ onOk, defaultValues }) => {
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
labelWidth={120}
|
||||
visibleProperties={{
|
||||
priceMode: true,
|
||||
margin: true,
|
||||
@ -76,7 +74,6 @@ const NewPart = ({ onOk, defaultValues }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
labelWidth={50}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -83,7 +83,6 @@ const ProductCategories = () => {
|
||||
cards={viewMode === 'cards'}
|
||||
visibleColumns={columnVisibility}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
|
||||
@ -19,7 +19,6 @@ const NewProductCategory = ({ onOk }) => {
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
labelWidth={70}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -39,7 +38,6 @@ const NewProductCategory = ({ onOk }) => {
|
||||
}}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
labelWidth={70}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -84,7 +84,6 @@ const ProductSkus = () => {
|
||||
type='productSku'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -23,7 +23,7 @@ const NewProductSku = ({ onOk, reset, defaultValues }) => {
|
||||
<ObjectInfo
|
||||
type='productSku'
|
||||
column={1}
|
||||
labelWidth={80}
|
||||
labelWidth={70}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
|
||||
@ -355,7 +355,6 @@ const Products = () => {
|
||||
type={'product'}
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -79,7 +79,6 @@ const TaxRates = () => {
|
||||
type='taxRate'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -5,10 +5,7 @@ import WizardView from '../../common/WizardView'
|
||||
|
||||
const NewTaxRate = ({ onOk, defaultValues }) => {
|
||||
return (
|
||||
<NewObjectForm
|
||||
type={'taxRate'}
|
||||
defaultValues={{ active: true, ...defaultValues }}
|
||||
>
|
||||
<NewObjectForm type={'taxRate'} defaultValues={{ ...defaultValues }}>
|
||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||
const steps = [
|
||||
{
|
||||
@ -22,7 +19,6 @@ const NewTaxRate = ({ onOk, defaultValues }) => {
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
labelWidth={100}
|
||||
/>
|
||||
)
|
||||
},
|
||||
@ -36,7 +32,6 @@ const NewTaxRate = ({ onOk, defaultValues }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
labelWidth={130}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
@ -55,7 +50,6 @@ const NewTaxRate = ({ onOk, defaultValues }) => {
|
||||
createdAt: false,
|
||||
updatedAt: false
|
||||
}}
|
||||
labelWidth={130}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
|
||||
@ -79,7 +79,6 @@ const Vendors = () => {
|
||||
type='vendor'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -21,7 +21,6 @@ const NewVendor = ({ onOk, defaultValues }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
labelWidth={80}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
@ -36,7 +35,6 @@ const NewVendor = ({ onOk, defaultValues }) => {
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
labelWidth={85}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
@ -56,7 +54,6 @@ const NewVendor = ({ onOk, defaultValues }) => {
|
||||
updatedAt: false
|
||||
}}
|
||||
isEditing={false}
|
||||
labelWidth={80}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -82,7 +82,6 @@ const GCodeFiles = () => {
|
||||
cards={viewMode === 'cards'}
|
||||
visibleColumns={columnVisibility}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -86,7 +86,6 @@ const Jobs = () => {
|
||||
visibleColumns={columnVisibility}
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -71,7 +71,6 @@ const SubJobs = () => {
|
||||
visibleColumns={columnVisibility}
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
|
||||
@ -79,7 +79,6 @@ const Clients = () => {
|
||||
type='client'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
@ -102,3 +101,4 @@ const Clients = () => {
|
||||
}
|
||||
|
||||
export default Clients
|
||||
|
||||
|
||||
@ -80,7 +80,6 @@ const Listings = () => {
|
||||
type='listing'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -80,7 +80,6 @@ const Marketplaces = () => {
|
||||
type='marketplace'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
|
||||
@ -80,7 +80,6 @@ const SalesOrders = () => {
|
||||
type='salesOrder'
|
||||
cards={viewMode === 'cards'}
|
||||
showFilterSidebar={showFilterSidebar}
|
||||
expandHeight={true}
|
||||
/>
|
||||
</Flex>
|
||||
<Modal
|
||||
@ -106,3 +105,4 @@ const SalesOrders = () => {
|
||||
}
|
||||
|
||||
export default SalesOrders
|
||||
|
||||
|
||||
@ -152,8 +152,8 @@ const DashboardNavigation = () => {
|
||||
style={{
|
||||
fontSize: '46px',
|
||||
height: '16px',
|
||||
marginLeft: '13px',
|
||||
marginRight: '0px'
|
||||
marginLeft: '15px',
|
||||
marginRight: '8px'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@ -225,7 +225,7 @@ const DashboardNavigation = () => {
|
||||
align='center'
|
||||
style={{ marginTop: '-2px', marginRight: '6px' }}
|
||||
>
|
||||
<Space style={{ paddingTop: '2px', marginRight: '8px' }}>
|
||||
<Space>
|
||||
<WebAppSwitcher />
|
||||
<KeyboardShortcut
|
||||
shortcut='alt+q'
|
||||
|
||||
@ -39,7 +39,7 @@ const DashboardWindowButtons = () => {
|
||||
<div style={{ width: '80px' }} />
|
||||
) : null
|
||||
) : (
|
||||
<div style={{ width: '95px', marginRight: '2.5px' }}>
|
||||
<div style={{ width: '95px' }}>
|
||||
<Flex style={{ position: 'relative', zIndex: 9999 }}>
|
||||
{maximizeButton}
|
||||
{minimizeButton}
|
||||
|
||||
@ -101,8 +101,7 @@ const ObjectTable = forwardRef(
|
||||
masterFilter = {},
|
||||
size = 'middle',
|
||||
onStateChange,
|
||||
showFilterSidebar = false,
|
||||
expandHeight = false
|
||||
showFilterSidebar = false
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
@ -729,30 +728,6 @@ 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)
|
||||
@ -903,10 +878,7 @@ const ObjectTable = forwardRef(
|
||||
return (
|
||||
<Row
|
||||
gutter={[16, 16]}
|
||||
style={{
|
||||
overflowY: 'auto',
|
||||
maxHeight: adjustedScrollHeight
|
||||
}}
|
||||
style={{ overflowY: 'auto', maxHeight: adjustedScrollHeight }}
|
||||
ref={cardsContainerRef}
|
||||
>
|
||||
{tableData.map((record) => {
|
||||
@ -1030,8 +1002,7 @@ ObjectTable.propTypes = {
|
||||
masterFilter: PropTypes.object,
|
||||
size: PropTypes.string,
|
||||
onStateChange: PropTypes.func,
|
||||
showFilterSidebar: PropTypes.bool,
|
||||
expandHeight: PropTypes.bool
|
||||
showFilterSidebar: PropTypes.bool
|
||||
}
|
||||
|
||||
export default ObjectTable
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { useContext } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Button } from 'antd'
|
||||
import { Button, Tooltip } 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)
|
||||
@ -43,14 +42,17 @@ const WebAppSwitcher = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<KeyboardShortcut shortcut='alt+w' hint='ALT W' onTrigger={handleClick}>
|
||||
<Tooltip
|
||||
title={isElectron ? 'Open in browser' : 'Open in app'}
|
||||
arrow={false}
|
||||
>
|
||||
<Button
|
||||
icon={isElectron ? <OpenInBrowserIcon /> : <OpenInAppIcon />}
|
||||
type='text'
|
||||
style={{ marginTop: '4px' }}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
</KeyboardShortcut>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -30,11 +30,9 @@ import IdDisplay from '../common/IdDisplay'
|
||||
import config from '../../../config'
|
||||
import {
|
||||
getModelByName,
|
||||
getModelByPrefix,
|
||||
searchModelsByLabel
|
||||
getModelByPrefix
|
||||
} 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'
|
||||
@ -130,22 +128,6 @@ 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) &&
|
||||
@ -153,9 +135,9 @@ const SpotlightContent = ({
|
||||
!Array.isArray(data) &&
|
||||
typeof data === 'object'
|
||||
) {
|
||||
setListData([...models, data])
|
||||
setListData([data])
|
||||
} else {
|
||||
setListData([...models, ...data])
|
||||
setListData(data)
|
||||
}
|
||||
|
||||
// Check if there's a pending query after this fetch completes
|
||||
@ -308,11 +290,7 @@ const SpotlightContent = ({
|
||||
const item = listData[0]
|
||||
let type = item.type || item.objectType || inputPrefix?.type
|
||||
const model = getModelByName(type)
|
||||
const defaultAction = item?.type
|
||||
? getDefaultRowAction(item)
|
||||
: model
|
||||
? getDefaultRowAction(model)
|
||||
: null
|
||||
const defaultAction = model ? getDefaultRowAction(model) : null
|
||||
if (defaultAction) {
|
||||
triggerRowAction(defaultAction, item)
|
||||
}
|
||||
@ -326,11 +304,7 @@ const SpotlightContent = ({
|
||||
const item = listData[index]
|
||||
let type = item.type || item.objectType || inputPrefix?.type
|
||||
const model = getModelByName(type)
|
||||
const defaultAction = item?.type
|
||||
? getDefaultRowAction(item)
|
||||
: model
|
||||
? getDefaultRowAction(model)
|
||||
: null
|
||||
const defaultAction = model ? getDefaultRowAction(model) : null
|
||||
if (defaultAction) {
|
||||
triggerRowAction(defaultAction, item)
|
||||
}
|
||||
@ -473,12 +447,10 @@ const SpotlightContent = ({
|
||||
<List
|
||||
dataSource={listData}
|
||||
renderItem={(item, index) => {
|
||||
let type = item.objectType || inputPrefix?.type || item?.type
|
||||
let type = item.objectType || inputPrefix?.type
|
||||
const model = getModelByName(type)
|
||||
const Icon = model?.icon
|
||||
const rowActions = item?.type
|
||||
? item.actions
|
||||
: getRowActions(model)
|
||||
const rowActions = getRowActions(model)
|
||||
let shortcutText = ''
|
||||
if (index === 0) {
|
||||
shortcutText = 'ENTER'
|
||||
@ -520,9 +492,7 @@ const SpotlightContent = ({
|
||||
showId={false}
|
||||
/>
|
||||
) : null}
|
||||
{item?._id ? (
|
||||
<IdDisplay id={item._id} type={type} longId={false} />
|
||||
) : null}
|
||||
</Flex>
|
||||
<Flex gap={'small'}>
|
||||
{rowActions
|
||||
|
||||
@ -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
|
||||
export const getPropertyValue = (obj, path) => {
|
||||
if (!obj || !path) return undefined
|
||||
|
||||
@ -8,8 +8,6 @@ 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: [
|
||||
|
||||
@ -3,8 +3,6 @@ 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: [],
|
||||
|
||||
@ -8,8 +8,6 @@ import BinIcon from '../../components/Icons/BinIcon'
|
||||
export const Client = {
|
||||
name: 'client',
|
||||
label: 'Client',
|
||||
labelPlural: 'Clients',
|
||||
url: '/dashboard/sales/clients',
|
||||
prefix: 'CLI',
|
||||
icon: ClientIcon,
|
||||
actions: [
|
||||
|
||||
@ -8,8 +8,6 @@ import BinIcon from '../../components/Icons/BinIcon'
|
||||
export const Courier = {
|
||||
name: 'courier',
|
||||
label: 'Courier',
|
||||
labelPlural: 'Couriers',
|
||||
url: '/dashboard/management/couriers',
|
||||
prefix: 'COR',
|
||||
icon: CourierIcon,
|
||||
actions: [
|
||||
|
||||
@ -8,8 +8,6 @@ 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: [
|
||||
|
||||
@ -8,8 +8,6 @@ import dayjs from 'dayjs'
|
||||
export const DocumentJob = {
|
||||
name: 'documentJob',
|
||||
label: 'Document Job',
|
||||
labelPlural: 'Document Jobs',
|
||||
url: '/dashboard/management/documentjobs',
|
||||
prefix: 'DJB',
|
||||
icon: DocumentJobIcon,
|
||||
actions: [
|
||||
|
||||
@ -7,8 +7,6 @@ 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: [
|
||||
|
||||
@ -7,8 +7,6 @@ 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: [
|
||||
|
||||
@ -8,8 +8,6 @@ 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: [
|
||||
|
||||
@ -9,8 +9,6 @@ import BinIcon from '../../components/Icons/BinIcon'
|
||||
export const Filament = {
|
||||
name: 'filament',
|
||||
label: 'Filament',
|
||||
labelPlural: 'Filaments',
|
||||
url: '/dashboard/management/filaments',
|
||||
prefix: 'FIL',
|
||||
icon: FilamentIcon,
|
||||
actions: [
|
||||
@ -90,14 +88,7 @@ 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: [
|
||||
{
|
||||
@ -177,7 +168,7 @@ export const Filament = {
|
||||
{
|
||||
name: 'cost',
|
||||
label: 'Cost',
|
||||
required: true,
|
||||
required: false,
|
||||
columnWidth: 100,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
@ -187,7 +178,7 @@ export const Filament = {
|
||||
{
|
||||
name: 'costWithTax',
|
||||
label: 'Cost w/ Tax',
|
||||
required: true,
|
||||
required: false,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
@ -210,7 +201,7 @@ export const Filament = {
|
||||
{
|
||||
name: 'costTaxRate',
|
||||
label: 'Cost Tax Rate',
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'object',
|
||||
objectType: 'taxRate',
|
||||
showHyperlink: true,
|
||||
|
||||
@ -8,8 +8,6 @@ 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: [
|
||||
@ -63,6 +61,7 @@ export const FilamentSku = {
|
||||
`/dashboard/management/filamentskus/info?filamentSkuId=${_id}&action=delete`
|
||||
}
|
||||
],
|
||||
url: (id) => `/dashboard/management/filamentskus/info?filamentSkuId=${id}`,
|
||||
columns: [
|
||||
'_reference',
|
||||
'name',
|
||||
@ -171,42 +170,36 @@ export const FilamentSku = {
|
||||
{
|
||||
name: 'overrideCost',
|
||||
label: 'Override Cost',
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'bool',
|
||||
value: (objectData) => objectData?.overrideCost ?? false,
|
||||
columnWidth: 150
|
||||
},
|
||||
{
|
||||
name: 'cost',
|
||||
label: 'Cost',
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
visible: (objectData) => {
|
||||
return objectData?.overrideCost
|
||||
},
|
||||
disabled: (objectData) => !objectData?.overrideCost,
|
||||
value: (objectData) =>
|
||||
objectData?.overrideCost
|
||||
? objectData?.cost
|
||||
: objectData?.filament?.cost,
|
||||
objectData?.overrideCost ? objectData?.cost : undefined,
|
||||
columnWidth: 100
|
||||
},
|
||||
{
|
||||
name: 'costWithTax',
|
||||
label: 'Cost w/ Tax',
|
||||
required: true,
|
||||
required: false,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
step: 0.01,
|
||||
visible: (objectData) => {
|
||||
return objectData?.overrideCost
|
||||
},
|
||||
disabled: (objectData) => !objectData?.overrideCost,
|
||||
value: (objectData) => {
|
||||
if (!objectData?.overrideCost)
|
||||
return objectData?.filament?.costWithTax || undefined
|
||||
if (!objectData?.overrideCost) return undefined
|
||||
const cost = objectData?.cost
|
||||
const taxRate = objectData?.costTaxRate
|
||||
if (!cost) return 0
|
||||
@ -222,17 +215,13 @@ export const FilamentSku = {
|
||||
{
|
||||
name: 'costTaxRate',
|
||||
label: 'Cost Tax Rate',
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'object',
|
||||
objectType: 'taxRate',
|
||||
showHyperlink: true,
|
||||
visible: (objectData) => {
|
||||
return objectData?.overrideCost
|
||||
},
|
||||
disabled: (objectData) => !objectData?.overrideCost,
|
||||
value: (objectData) =>
|
||||
objectData?.overrideCost
|
||||
? objectData?.costTaxRate
|
||||
: objectData?.filament?.costTaxRate,
|
||||
objectData?.overrideCost ? objectData?.costTaxRate : undefined,
|
||||
columnWidth: 150
|
||||
}
|
||||
]
|
||||
|
||||
@ -4,8 +4,6 @@ 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: [
|
||||
|
||||
@ -9,8 +9,6 @@ import BinIcon from '../../components/Icons/BinIcon'
|
||||
export const File = {
|
||||
name: 'file',
|
||||
label: 'File',
|
||||
labelPlural: 'Files',
|
||||
url: '/dashboard/management/files',
|
||||
prefix: 'FLE',
|
||||
icon: FileIcon,
|
||||
actions: [
|
||||
@ -71,6 +69,7 @@ export const File = {
|
||||
`/dashboard/management/files/info?fileId=${_id}&action=delete`
|
||||
}
|
||||
],
|
||||
url: (id) => `/dashboard/management/files/info?fileId=${id}`,
|
||||
columns: [
|
||||
'_reference',
|
||||
'name',
|
||||
|
||||
@ -9,8 +9,6 @@ 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: [
|
||||
|
||||
@ -9,8 +9,6 @@ import OTPIcon from '../../components/Icons/OTPIcon'
|
||||
export const Host = {
|
||||
name: 'host',
|
||||
label: 'Host',
|
||||
labelPlural: 'Hosts',
|
||||
url: '/dashboard/management/hosts',
|
||||
prefix: 'HST',
|
||||
icon: HostIcon,
|
||||
actions: [
|
||||
|
||||
@ -4,7 +4,6 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
export const Initial = {
|
||||
name: 'initial',
|
||||
label: 'Initial',
|
||||
labelPlural: 'Initials',
|
||||
prefix: 'INT',
|
||||
icon: QuestionCircleIcon,
|
||||
actions: [
|
||||
@ -17,4 +16,5 @@ export const Initial = {
|
||||
url: (_id) => `/dashboard/management/initials/info?initialId=${_id}`
|
||||
}
|
||||
],
|
||||
url: () => `#`
|
||||
}
|
||||
|
||||
@ -9,8 +9,6 @@ import PlusIcon from '../../components/Icons/PlusIcon'
|
||||
export const Invoice = {
|
||||
name: 'invoice',
|
||||
label: 'Invoice',
|
||||
labelPlural: 'Invoices',
|
||||
url: '/dashboard/finance/invoices',
|
||||
prefix: 'INV',
|
||||
icon: InvoiceIcon,
|
||||
actions: [
|
||||
|
||||
@ -6,8 +6,6 @@ import dayjs from 'dayjs'
|
||||
export const Job = {
|
||||
name: 'job',
|
||||
label: 'Job',
|
||||
labelPlural: 'Jobs',
|
||||
url: '/dashboard/production/jobs',
|
||||
prefix: 'JOB',
|
||||
icon: JobIcon,
|
||||
actions: [
|
||||
|
||||
@ -9,8 +9,6 @@ import PlusIcon from '../../components/Icons/PlusIcon'
|
||||
export const Listing = {
|
||||
name: 'listing',
|
||||
label: 'Listing',
|
||||
labelPlural: 'Listings',
|
||||
url: '/dashboard/sales/listings',
|
||||
prefix: 'LST',
|
||||
icon: ListingIcon,
|
||||
actions: [
|
||||
|
||||
@ -8,7 +8,6 @@ import BinIcon from '../../components/Icons/BinIcon'
|
||||
export const ListingVarient = {
|
||||
name: 'listingVarient',
|
||||
label: 'Listing Varient',
|
||||
labelPlural: 'Listing Varients',
|
||||
prefix: 'LVR',
|
||||
icon: ListingVarientIcon,
|
||||
actions: [
|
||||
|
||||
@ -10,8 +10,6 @@ import BinIcon from '../../components/Icons/BinIcon'
|
||||
export const Marketplace = {
|
||||
name: 'marketplace',
|
||||
label: 'Marketplace',
|
||||
labelPlural: 'Marketplaces',
|
||||
url: '/dashboard/sales/marketplaces',
|
||||
prefix: 'MKT',
|
||||
icon: MarketplaceIcon,
|
||||
actions: [
|
||||
|
||||
@ -8,8 +8,6 @@ import BinIcon from '../../components/Icons/BinIcon'
|
||||
export const Material = {
|
||||
name: 'material',
|
||||
label: 'Material',
|
||||
labelPlural: 'Materials',
|
||||
url: '/dashboard/management/materials',
|
||||
prefix: 'MAT',
|
||||
icon: MaterialIcon,
|
||||
actions: [
|
||||
@ -62,6 +60,7 @@ 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'],
|
||||
|
||||
@ -4,7 +4,6 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||
export const Note = {
|
||||
name: 'note',
|
||||
label: 'Note',
|
||||
labelPlural: 'Notes',
|
||||
prefix: 'NTE',
|
||||
icon: NoteIcon,
|
||||
actions: [
|
||||
|
||||
@ -7,8 +7,6 @@ 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: [
|
||||
|
||||
@ -8,8 +8,6 @@ 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: [
|
||||
|
||||
@ -8,8 +8,6 @@ import PlusIcon from '../../components/Icons/PlusIcon'
|
||||
export const Part = {
|
||||
name: 'part',
|
||||
label: 'Part',
|
||||
labelPlural: 'Parts',
|
||||
url: '/dashboard/management/parts',
|
||||
prefix: 'PRT',
|
||||
icon: PartIcon,
|
||||
actions: [
|
||||
@ -76,26 +74,8 @@ 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',
|
||||
@ -139,6 +119,13 @@ export const Part = {
|
||||
readOnly: true,
|
||||
columnWidth: 175
|
||||
},
|
||||
{
|
||||
name: 'fileName',
|
||||
label: 'File Name',
|
||||
required: false,
|
||||
type: 'text',
|
||||
columnWidth: 200
|
||||
},
|
||||
{
|
||||
name: 'file',
|
||||
label: 'File',
|
||||
@ -150,7 +137,7 @@ export const Part = {
|
||||
{
|
||||
name: 'cost',
|
||||
label: 'Cost',
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
@ -160,7 +147,7 @@ export const Part = {
|
||||
{
|
||||
name: 'costWithTax',
|
||||
label: 'Cost w/ Tax',
|
||||
required: true,
|
||||
required: false,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
@ -183,7 +170,7 @@ export const Part = {
|
||||
{
|
||||
name: 'costTaxRate',
|
||||
label: 'Cost Tax Rate',
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'object',
|
||||
objectType: 'taxRate',
|
||||
showHyperlink: true,
|
||||
@ -192,14 +179,14 @@ export const Part = {
|
||||
{
|
||||
name: 'priceMode',
|
||||
label: 'Price Mode',
|
||||
required: true,
|
||||
required: false,
|
||||
columnWidth: 150,
|
||||
type: 'priceMode'
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
label: 'Price',
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
min: 0,
|
||||
@ -224,31 +211,19 @@ export const Part = {
|
||||
{
|
||||
name: 'margin',
|
||||
label: 'Margin',
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'number',
|
||||
readOnly: (objectData) => objectData?.priceMode == 'amount',
|
||||
disabled: (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: true,
|
||||
required: false,
|
||||
readOnly: true,
|
||||
type: 'number',
|
||||
prefix: '£',
|
||||
@ -280,7 +255,7 @@ export const Part = {
|
||||
{
|
||||
name: 'priceTaxRate',
|
||||
label: 'Price Tax Rate',
|
||||
required: true,
|
||||
required: false,
|
||||
type: 'object',
|
||||
objectType: 'taxRate',
|
||||
showHyperlink: true,
|
||||
|
||||
@ -8,8 +8,6 @@ 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: [
|
||||
@ -62,6 +60,7 @@ export const PartSku = {
|
||||
`/dashboard/management/partskus/info?partSkuId=${_id}&action=delete`
|
||||
}
|
||||
],
|
||||
url: (id) => `/dashboard/management/partskus/info?partSkuId=${id}`,
|
||||
columns: [
|
||||
'_reference',
|
||||
'name',
|
||||
|
||||
@ -4,8 +4,6 @@ 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: [
|
||||
@ -18,6 +16,7 @@ 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: [
|
||||
|
||||
@ -8,8 +8,6 @@ import BinIcon from '../../components/Icons/BinIcon'
|
||||
export const Payment = {
|
||||
name: 'payment',
|
||||
label: 'Payment',
|
||||
labelPlural: 'Payments',
|
||||
url: '/dashboard/finance/payments',
|
||||
prefix: 'PAY',
|
||||
icon: PaymentIcon,
|
||||
actions: [
|
||||
|
||||
@ -14,8 +14,6 @@ import JobIcon from '../../components/Icons/JobIcon'
|
||||
export const Printer = {
|
||||
name: 'printer',
|
||||
label: 'Printer',
|
||||
labelPlural: 'Printers',
|
||||
url: '/dashboard/production/printers',
|
||||
prefix: 'PRN',
|
||||
icon: PrinterIcon,
|
||||
actions: [
|
||||
|
||||
@ -8,8 +8,6 @@ import PlusIcon from '../../components/Icons/PlusIcon'
|
||||
export const Product = {
|
||||
name: 'product',
|
||||
label: 'Product',
|
||||
labelPlural: 'Products',
|
||||
url: '/dashboard/management/products',
|
||||
prefix: 'PRD',
|
||||
icon: ProductIcon,
|
||||
actions: [
|
||||
@ -256,23 +254,11 @@ export const Product = {
|
||||
label: 'Margin',
|
||||
required: true,
|
||||
type: 'number',
|
||||
readOnly: (objectData) => objectData?.priceMode == 'amount',
|
||||
disabled: (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
|
||||
},
|
||||
{
|
||||
|
||||
@ -8,8 +8,6 @@ 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,
|
||||
@ -64,6 +62,8 @@ 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'],
|
||||
|
||||
@ -8,8 +8,6 @@ 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: [
|
||||
@ -62,6 +60,7 @@ export const ProductSku = {
|
||||
`/dashboard/management/productskus/info?productSkuId=${_id}&action=delete`
|
||||
}
|
||||
],
|
||||
url: (id) => `/dashboard/management/productskus/info?productSkuId=${id}`,
|
||||
columns: [
|
||||
'_reference',
|
||||
'name',
|
||||
|
||||
@ -8,8 +8,6 @@ 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: [
|
||||
@ -86,6 +84,7 @@ 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
Loading…
x
Reference in New Issue
Block a user