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 .",
"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"
}
}
}

View File

@ -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()
})
}

View File

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

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}
/>
)

View File

@ -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

View File

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

View File

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

View File

@ -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}
/>
)
}

View File

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

View File

@ -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}
/>
)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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}
/>

View File

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

View File

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

View File

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

View File

@ -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}
/>
)

View File

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

View File

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

View File

@ -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}
/>

View File

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

View File

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

View File

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

View File

@ -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}
/>
)

View File

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

View File

@ -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}
/>
)
}

View File

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

View File

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

View File

@ -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}
/>
)

View File

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

View File

@ -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}
/>
)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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}
/>

View File

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

View File

@ -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}
/>
)

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -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

View File

@ -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'

View File

@ -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}

View File

@ -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

View File

@ -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>
)
}

View File

@ -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

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
export const getPropertyValue = (obj, path) => {
if (!obj || !path) return undefined

View File

@ -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: [

View File

@ -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: [],

View File

@ -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: [

View File

@ -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: [

View File

@ -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: [

View File

@ -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: [

View File

@ -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: [

View File

@ -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: [

View File

@ -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: [

View File

@ -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,

View File

@ -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
}
]

View File

@ -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: [

View File

@ -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',

View File

@ -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: [

View File

@ -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: [

View File

@ -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: () => `#`
}

View File

@ -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: [

View File

@ -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: [

View File

@ -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: [

View File

@ -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: [

View File

@ -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: [

View File

@ -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'],

View File

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

View File

@ -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: [

View File

@ -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: [

View File

@ -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,

View File

@ -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',

View File

@ -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: [

View File

@ -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: [

View File

@ -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: [

View File

@ -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
},
{

View File

@ -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'],

View File

@ -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',

View File

@ -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