From cdad47b82f5950ac8a3d211d2c422b65631f0600 Mon Sep 17 00:00:00 2001 From: Dave Richer Date: Mon, 5 May 2025 10:37:42 -0400 Subject: [PATCH] feature/IO-3066-1-scaffolding: Fix some missing PR Notes around PPG --- src/main/graphql/queries.ts | 149 ++++++++++++- src/main/index.ts | 30 +-- src/main/ipc/ipcMainHandler.settings.ts | 59 ++--- src/main/ipc/paintScaleHandlers/PPG.ts | 211 +++++++++--------- src/main/setup-keep-alive-task.ts | 3 +- .../Settings.PaintScaleInputPaths.tsx | 13 +- .../Settings.PaintScaleOutputPaths.tsx | 23 +- 7 files changed, 330 insertions(+), 158 deletions(-) diff --git a/src/main/graphql/queries.ts b/src/main/graphql/queries.ts index d510526..e020c53 100644 --- a/src/main/graphql/queries.ts +++ b/src/main/graphql/queries.ts @@ -2,6 +2,7 @@ import { UUID } from "crypto"; import { parse, TypedQueryDocumentNode } from "graphql"; import { gql } from "graphql-request"; import { AvailableJobSchema } from "../decoder/decoder"; + // Define types for the query result and variables export interface ActiveBodyshopQueryResult { bodyshops: Array<{ @@ -11,9 +12,8 @@ export interface ActiveBodyshopQueryResult { convenient_company: string; }>; } -// No variables needed for this query -// Transform the string query into a TypedQueryDocumentNode +// No variables needed for this query export const QUERY_ACTIVE_BODYSHOP_TYPED: TypedQueryDocumentNode< ActiveBodyshopQueryResult, Record @@ -34,9 +34,11 @@ export interface MasterdataQueryResult { key: string; }>; } + interface MasterdataQueryVariables { key: string; } + export const QUERY_MASTERDATA_TYPED: TypedQueryDocumentNode< MasterdataQueryResult, MasterdataQueryVariables @@ -54,9 +56,11 @@ export interface VehicleQueryResult { id: UUID; }>; } + interface VehicleQueryVariables { vin: string; } + export const QUERY_VEHICLE_BY_VIN_TYPED: TypedQueryDocumentNode< VehicleQueryResult, VehicleQueryVariables @@ -73,9 +77,11 @@ export interface QueryJobByClmNoResult { id: UUID; }>; } + export interface QueryJobByClmNoVariables { clm_no: string; } + export const QUERY_JOB_BY_CLM_NO_TYPED: TypedQueryDocumentNode< QueryJobByClmNoResult, QueryJobByClmNoVariables @@ -92,9 +98,11 @@ export interface InsertAvailableJobResult { id: UUID; }>; } + export interface InsertAvailableJobVariables { jobInput: Array; } + export const INSERT_AVAILABLE_JOB_TYPED: TypedQueryDocumentNode< InsertAvailableJobResult, InsertAvailableJobVariables @@ -125,3 +133,140 @@ export const INSERT_AVAILABLE_JOB_TYPED: TypedQueryDocumentNode< InsertAvailableJobResult, InsertAvailableJobVariables >; + +// Add PpgData Query +export interface PpgDataQueryResult { + bodyshops_by_pk: { + id: string; + shopname: string; + imexshopid: string; + } | null; + jobs: Array<{ + id: string; + ro_number: string; + status: string; + ownr_fn: string; + ownr_ln: string; + ownr_co_nm: string; + v_vin: string; + v_model_yr: string; + v_make_desc: string; + v_model_desc: string; + v_color: string; + plate_no: string; + ins_co_nm: string; + est_ct_fn: string; + est_ct_ln: string; + rate_mapa: number; + rate_lab: number; + job_totals: { + rates?: { + mapa?: { + total?: { + amount?: number; + }; + }; + }; + totals?: { + subtotal?: { + amount?: number; + }; + }; + }; + vehicle: { + v_paint_codes: { + paint_cd1?: string; + }; + }; + labhrs: { + aggregate: { + sum: { + mod_lb_hrs: number; + }; + }; + }; + larhrs: { + aggregate: { + sum: { + mod_lb_hrs: number; + }; + }; + }; + }>; +} + +export interface PpgDataQueryVariables { + today: string; + todayplus5: string; + shopid: string; +} + +export const PPG_DATA_QUERY_TYPED: TypedQueryDocumentNode< + PpgDataQueryResult, + PpgDataQueryVariables +> = parse(gql` + query PpgData( + $today: timestamptz! + $todayplus5: timestamptz! + $shopid: uuid! + ) { + bodyshops_by_pk(id: $shopid) { + id + shopname + imexshopid + } + jobs( + where: { + _or: [ + { + _and: [ + { scheduled_in: { _lte: $todayplus5 } } + { scheduled_in: { _gte: $today } } + ] + } + { inproduction: { _eq: true } } + ] + } + ) { + id + ro_number + status + ownr_fn + ownr_ln + ownr_co_nm + v_vin + v_model_yr + v_make_desc + v_model_desc + v_color + plate_no + ins_co_nm + est_ct_fn + est_ct_ln + rate_mapa + rate_lab + job_totals + vehicle { + v_paint_codes + } + labhrs: joblines_aggregate( + where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } } + ) { + aggregate { + sum { + mod_lb_hrs + } + } + } + larhrs: joblines_aggregate( + where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } } + ) { + aggregate { + sum { + mod_lb_hrs + } + } + } + } + } +`) as TypedQueryDocumentNode; diff --git a/src/main/index.ts b/src/main/index.ts index d60d8e3..2245380 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -464,27 +464,32 @@ app.whenReady().then(async () => { if (url.startsWith(`${protocol}://keep-alive`)) { log.info("Keep-alive protocol received, app is already running."); // Do nothing if already running - return; // Skip openMainWindow to avoid focusing the window + return; } else { openInExplorer(url); } - } else { - openMainWindow(); // Focus window if no URL } + // No action taken if no URL is provided }); //Dynamically load ipcMain handlers once ready. try { - const module = await import("./ipc/ipcMainConfig"); + const { initializeCronTasks } = await import("./ipc/ipcMainConfig"); log.debug("Successfully loaded ipcMainConfig"); - // Initialize cron tasks after loading ipcMainConfig - await module.initializeCronTasks(); - log.info("Cron tasks initialized successfully"); + try { + await initializeCronTasks(); + log.info("Cron tasks initialized successfully"); + } catch (error) { + log.warn("Non-fatal: Failed to initialize cron tasks", { + ...ErrorTypeCheck(error), + }); + } } catch (error) { - log.error("Failed to load ipcMainConfig or initialize cron tasks", { + log.error("Fatal: Failed to load ipcMainConfig", { ...ErrorTypeCheck(error), }); + throw error; // Adjust based on whether the app should continue } //Create Tray @@ -568,15 +573,10 @@ app.whenReady().then(async () => { app.on("open-url", (event: Electron.Event, url: string) => { event.preventDefault(); - //Don't do anything for now. We just want to open the app. if (url.startsWith(`${protocol}://keep-alive`)) { log.info("Keep-alive protocol received."); - if (BrowserWindow.getAllWindows().length === 0) { - isKeepAliveLaunch = true; - openMainWindow(); // Launch app if not running - } - // Do nothing if already running - return; // Skip openMainWindow to avoid focusing the window + // Do nothing, whether app is running or not + return; } else { openInExplorer(url); } diff --git a/src/main/ipc/ipcMainHandler.settings.ts b/src/main/ipc/ipcMainHandler.settings.ts index b71b636..4936b91 100644 --- a/src/main/ipc/ipcMainHandler.settings.ts +++ b/src/main/ipc/ipcMainHandler.settings.ts @@ -12,7 +12,6 @@ import { } from "../watcher/watcher"; import { PaintScaleConfig } from "../../util/types/paintScale"; - // Initialize paint scale input configs in store if not set if (!Store.get("settings.paintScaleInputConfigs")) { Store.set("settings.paintScaleInputConfigs", []); @@ -35,8 +34,8 @@ const SettingsWatchedFilePathsAdd = async (): Promise => { if (!result.canceled) { Store.set( - "settings.filepaths", - _.union(result.filePaths, Store.get("settings.filepaths")), + "settings.filepaths", + _.union(result.filePaths, Store.get("settings.filepaths")), ); addWatcherPath(result.filePaths); } @@ -45,12 +44,12 @@ const SettingsWatchedFilePathsAdd = async (): Promise => { }; const SettingsWatchedFilePathsRemove = async ( - _event: IpcMainInvokeEvent, - path: string, + _event: IpcMainInvokeEvent, + path: string, ): Promise => { Store.set( - "settings.filepaths", - _.without(Store.get("settings.filepaths"), path), + "settings.filepaths", + _.without(Store.get("settings.filepaths"), path), ); removeWatcherPath(path); return Store.get("settings.filepaths"); @@ -65,16 +64,16 @@ const SettingsWatcherPollingGet = async (): Promise<{ interval: number; }> => { const pollingEnabled: { enabled: boolean; interval: number } = - Store.get("settings.polling"); + Store.get("settings.polling"); return { enabled: pollingEnabled.enabled, interval: pollingEnabled.interval }; }; const SettingsWatcherPollingSet = async ( - _event: IpcMainInvokeEvent, - pollingSettings: { - enabled: boolean; - interval: number; - }, + _event: IpcMainInvokeEvent, + pollingSettings: { + enabled: boolean; + interval: number; + }, ): Promise<{ enabled: boolean; interval: number; @@ -131,11 +130,13 @@ const SettingEmsOutFilePathSet = async (): Promise => { return (Store.get("settings.emsOutFilePath") as string) || ""; }; -const SettingsPaintScaleInputConfigsGet = async ( - _event?: IpcMainInvokeEvent, -): Promise => { +const SettingsPaintScaleInputConfigsGet = ( + _event?: IpcMainInvokeEvent, +): PaintScaleConfig[] => { try { - const configs = Store.get("settings.paintScaleInputConfigs") as PaintScaleConfig[]; + const configs = Store.get( + "settings.paintScaleInputConfigs", + ) as PaintScaleConfig[]; log.debug("Retrieved paint scale input configs:", configs); return configs || []; } catch (error) { @@ -145,8 +146,8 @@ const SettingsPaintScaleInputConfigsGet = async ( }; const SettingsPaintScaleInputConfigsSet = async ( - _event: IpcMainInvokeEvent, - configs: PaintScaleConfig[], + _event: IpcMainInvokeEvent, + configs: PaintScaleConfig[], ): Promise => { try { Store.set("settings.paintScaleInputConfigs", configs); @@ -159,7 +160,7 @@ const SettingsPaintScaleInputConfigsSet = async ( }; const SettingsPaintScaleInputPathSet = async ( - _event: IpcMainInvokeEvent, + _event: IpcMainInvokeEvent, ): Promise => { try { const mainWindow = getMainWindow(); @@ -183,11 +184,13 @@ const SettingsPaintScaleInputPathSet = async ( } }; -const SettingsPaintScaleOutputConfigsGet = async ( - _event?: IpcMainInvokeEvent, -): Promise => { +const SettingsPaintScaleOutputConfigsGet = ( + _event?: IpcMainInvokeEvent, +): PaintScaleConfig[] => { try { - const configs = Store.get("settings.paintScaleOutputConfigs") as PaintScaleConfig[]; + const configs = Store.get( + "settings.paintScaleOutputConfigs", + ) as PaintScaleConfig[]; log.debug("Retrieved paint scale output configs:", configs); return configs || []; } catch (error) { @@ -197,8 +200,8 @@ const SettingsPaintScaleOutputConfigsGet = async ( }; const SettingsPaintScaleOutputConfigsSet = async ( - _event: IpcMainInvokeEvent, - configs: PaintScaleConfig[], + _event: IpcMainInvokeEvent, + configs: PaintScaleConfig[], ): Promise => { try { Store.set("settings.paintScaleOutputConfigs", configs); @@ -211,7 +214,7 @@ const SettingsPaintScaleOutputConfigsSet = async ( }; const SettingsPaintScaleOutputPathSet = async ( - _event: IpcMainInvokeEvent, + _event: IpcMainInvokeEvent, ): Promise => { try { const mainWindow = getMainWindow(); @@ -251,4 +254,4 @@ export { SettingsPaintScaleOutputConfigsGet, SettingsPaintScaleOutputConfigsSet, SettingsPaintScaleOutputPathSet, -}; \ No newline at end of file +}; diff --git a/src/main/ipc/paintScaleHandlers/PPG.ts b/src/main/ipc/paintScaleHandlers/PPG.ts index da66331..50cad7f 100644 --- a/src/main/ipc/paintScaleHandlers/PPG.ts +++ b/src/main/ipc/paintScaleHandlers/PPG.ts @@ -5,24 +5,43 @@ import axios from "axios"; import { create } from "xmlbuilder2"; import { parseStringPromise } from "xml2js"; import store from "../../store/store"; -import client from "../../graphql/graphql-client"; +import client, { getTokenFromRenderer } from "../../graphql/graphql-client"; import { PaintScaleConfig } from "../../../util/types/paintScale"; +import dayjs from "dayjs"; +import { + PPG_DATA_QUERY_TYPED, + PpgDataQueryResult, + PpgDataQueryVariables, +} from "../../graphql/queries"; -// PPG Input Handler export async function ppgInputHandler(config: PaintScaleConfig): Promise { try { log.info( `Polling input directory for PPG config ${config.id}: ${config.path}`, ); + log.debug( + `Archive dir: ${path.join(config.path!, "archive")}, Error dir: ${path.join(config.path!, "error")}`, + ); + // Ensure archive and error directories exist const archiveDir = path.join(config.path!, "archive"); const errorDir = path.join(config.path!, "error"); - await fs.mkdir(archiveDir, { recursive: true }); - await fs.mkdir(errorDir, { recursive: true }); + try { + await fs.mkdir(archiveDir, { recursive: true }); + await fs.mkdir(errorDir, { recursive: true }); + log.debug( + `Archive and error directories ensured: ${archiveDir}, ${errorDir}`, + ); + } catch (dirError) { + log.error(`Failed to create directories for ${config.path}:`, dirError); + throw dirError; + } // Check for files const files = await fs.readdir(config.path!); + log.debug(`Found ${files.length} files in ${config.path}:`, files); + for (const file of files) { // Only process XML files if (!file.toLowerCase().endsWith(".xml")) { @@ -30,8 +49,13 @@ export async function ppgInputHandler(config: PaintScaleConfig): Promise { } const filePath = path.join(config.path!, file); - const stats = await fs.stat(filePath); - if (!stats.isFile()) { + try { + const stats = await fs.stat(filePath); + if (!stats.isFile()) { + continue; + } + } catch (statError) { + log.warn(`Failed to stat file ${filePath}:`, statError); continue; } @@ -46,40 +70,61 @@ export async function ppgInputHandler(config: PaintScaleConfig): Promise { } // Validate XML structure - let xmlContent : BlobPart; - + let xmlContent: BlobPart; try { xmlContent = await fs.readFile(filePath, "utf8"); await parseStringPromise(xmlContent); + log.debug(`Successfully validated XML for ${filePath}`); } catch (error) { log.error(`Invalid XML in ${filePath}:`, error); - const timestamp = Date.now().toString(); // similar to DateTime.Now.Ticks in C# - const errorPath = path.join(errorDir, `${timestamp}.xml`); - await fs.rename(filePath, errorPath); - log.debug(`Moved invalid file to error: ${errorPath}`); + const timestamp = dayjs().format("YYYYMMDD_HHmmss"); + const originalFilename = path.basename(file, path.extname(file)); + const errorPath = path.join( + errorDir, + `${originalFilename}-${timestamp}.xml`, + ); + try { + await fs.rename(filePath, errorPath); + log.debug(`Moved invalid file to error: ${errorPath}`); + } catch (moveError) { + log.error( + `Failed to move invalid file to error directory ${errorPath}:`, + moveError, + ); + } continue; } // Get authentication token - const token = (store.get("user") as any)?.stsTokenManager?.accessToken; - if (!token) { - log.error(`No authentication token for file: ${filePath}`); + let token: string | null; + try { + token = await getTokenFromRenderer(); + if (!token) { + log.error(`No authentication token for file: ${filePath}`); + continue; + } + log.debug( + `Obtained authentication token for ${filePath}: ${token.slice(0, 10)}...`, + ); + } catch (tokenError) { + log.error( + `Failed to obtain authentication token for ${filePath}:`, + tokenError, + ); continue; } // Upload file to API const formData = new FormData(); formData.append("file", new Blob([xmlContent]), path.basename(filePath)); - formData.append( - "shopId", - (store.get("app.bodyshop") as any)?.shopname || "", - ); + const shopId = (store.get("app.bodyshop") as any)?.shopname || ""; + formData.append("shopId", shopId); + log.debug(`Shop ID: ${shopId}`); const baseURL = store.get("app.isTest") ? import.meta.env.VITE_API_TEST_URL : import.meta.env.VITE_API_URL; const finalUrl = `${baseURL}/mixdata/upload`; - log.debug(`Uploading file to ${finalUrl}`); try { @@ -88,23 +133,52 @@ export async function ppgInputHandler(config: PaintScaleConfig): Promise { Authorization: `Bearer ${token}`, "Content-Type": "multipart/form-data", }, + timeout: 10000, // 10-second timeout + }); + + log.info(`Upload response for ${filePath}:`, { + status: response.status, + statusText: response.statusText, + data: response.data, }); if (response.status === 200) { log.info(`Successful upload of ${filePath}`); // Move file to archive - const timestamp = Date.now().toString(); // generate new timestamp - const archivePath = path.join(archiveDir, `${timestamp}.xml`); - await fs.rename(filePath, archivePath); - log.debug(`Moved file to archive: ${archivePath}`); + const timestamp = dayjs().format("YYYYMMDD_HHmmss"); + const originalFilename = path.basename(file, path.extname(file)); + const archivePath = path.join( + archiveDir, + `${originalFilename}-${timestamp}.xml`, + ); + try { + await fs.access(archiveDir, fs.constants.W_OK); // Verify archiveDir is writable + await fs.rename(filePath, archivePath); + log.info(`Moved file to archive: ${archivePath}`); + } catch (moveError) { + log.error( + `Failed to move file to archive directory ${archivePath}:`, + moveError, + ); + } } else { log.error( `Failed to upload ${filePath}: ${response.status} ${response.statusText}`, - response.data, + { responseData: response.data }, ); } - } catch (error) { - log.error(`Error uploading ${filePath}:`, error); + } catch (error: any) { + log.error(`Error uploading ${filePath}:`, { + message: error.message, + code: error.code, + response: error.response + ? { + status: error.response.status, + statusText: error.response.statusText, + data: error.response.data, + } + : null, + }); } } } catch (error) { @@ -121,70 +195,16 @@ export async function ppgOutputHandler( await fs.mkdir(config.path!, { recursive: true }); - const query = ` - query PpgData($today: timestamptz!, $todayplus5: timestamptz!, $shopid: uuid!) { - bodyshops_by_pk(id:$shopid) { - id - shopname - imexshopid - } - jobs(where: { - _or: [ - { - _and: [ - { scheduled_in: { _lte: $todayplus5 } }, - { scheduled_in: { _gte: $today } } - ] - }, - { inproduction: { _eq: true } } - ] - }) { - id - ro_number - status - ownr_fn - ownr_ln - ownr_co_nm - v_vin - v_model_yr - v_make_desc - v_model_desc - v_color - plate_no - ins_co_nm - est_ct_fn - est_ct_ln - rate_mapa - rate_lab - job_totals - vehicle { - v_paint_codes - } - labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) { - aggregate { - sum { - mod_lb_hrs - } - } - } - larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) { - aggregate { - sum { - mod_lb_hrs - } - } - } - } - } -`; - - const variables = { - today: new Date().toISOString(), - todayplus5: new Date(Date.now() + 5 * 86400000).toISOString(), + const variables: PpgDataQueryVariables = { + today: dayjs().toISOString(), + todayplus5: dayjs().add(5, "day").toISOString(), shopid: (store.get("app.bodyshop") as any)?.id, }; - const response = (await client.request(query, variables)) as any; + const response = await client.request< + PpgDataQueryResult, + PpgDataQueryVariables + >(PPG_DATA_QUERY_TYPED, variables); const jobs = response.jobs ?? []; const header = { @@ -197,18 +217,10 @@ export async function ppgOutputHandler( }, Transaction: { TransactionID: "", - TransactionDate: (() => { - const now = new Date(); - const year = now.getFullYear(); - const month = ("0" + (now.getMonth() + 1)).slice(-2); - const day = ("0" + now.getDate()).slice(-2); - const hours = ("0" + now.getHours()).slice(-2); - const minutes = ("0" + now.getMinutes()).slice(-2); - return `${year}-${month}-${day}:${hours}:${minutes}`; - })(), + TransactionDate: dayjs().format("YYYY-MM-DD:HH:mm"), }, Product: { - Name: "ImEX Online", + Name: import.meta.env.VITE_COMPANY === "IMEX", Version: "", }, }, @@ -220,7 +232,7 @@ export async function ppgOutputHandler( }, RepairOrders: { ROCount: jobs.length.toString(), - RO: jobs.map((job: any) => ({ + RO: jobs.map((job) => ({ RONumber: job.ro_number || "", ROStatus: "Open", Customer: `${job.ownr_ln || ""}, ${job.ownr_fn || ""}`, @@ -258,4 +270,3 @@ export async function ppgOutputHandler( log.error(`Error generating PPG output for config ${config.id}:`, error); } } - diff --git a/src/main/setup-keep-alive-task.ts b/src/main/setup-keep-alive-task.ts index a0af962..6e16f74 100644 --- a/src/main/setup-keep-alive-task.ts +++ b/src/main/setup-keep-alive-task.ts @@ -6,9 +6,9 @@ const execPromise = promisify(exec); // Define the interval as a variable (in minutes) const KEEP_ALIVE_INTERVAL_MINUTES = 15; +const taskName = "ShopPartnerKeepAlive"; export async function setupKeepAliveTask(): Promise { - const taskName = "ImEXShopPartnerKeepAlive"; const protocolUrl = "imexmedia://keep-alive"; // Use rundll32.exe to silently open the URL as a protocol const command = `rundll32.exe url.dll,OpenURL "${protocolUrl}"`; @@ -30,7 +30,6 @@ export async function setupKeepAliveTask(): Promise { } export async function isKeepAliveTaskInstalled(): Promise { - const taskName = "ImEXShopPartnerKeepAlive"; const maxRetries = 3; const retryDelay = 500; // 500ms delay between retries diff --git a/src/renderer/src/components/Settings/Settings.PaintScaleInputPaths.tsx b/src/renderer/src/components/Settings/Settings.PaintScaleInputPaths.tsx index 444047b..02118d9 100644 --- a/src/renderer/src/components/Settings/Settings.PaintScaleInputPaths.tsx +++ b/src/renderer/src/components/Settings/Settings.PaintScaleInputPaths.tsx @@ -13,9 +13,10 @@ import { Space, Table, Tag, + theme, Tooltip, } from "antd"; -import { FC, useState } from "react"; +import { JSX, useState } from "react"; import { useTranslation } from "react-i18next"; import { PaintScaleConfig, @@ -24,8 +25,10 @@ import { } from "../../../../util/types/paintScale"; import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig"; -const SettingsPaintScaleInputPaths: FC = () => { +const SettingsPaintScaleInputPaths = (): JSX.Element => { const { t } = useTranslation(); + const { token } = theme.useToken(); // Access theme tokens + const { paintScaleConfigs, handleAddConfig, @@ -87,7 +90,7 @@ const SettingsPaintScaleInputPaths: FC = () => { placeholder={t("settings.labels.paintScalePath")} disabled style={{ - borderColor: isValid ? "#52c41a" : "#d9d9d9", + borderColor: isValid ? token.colorSuccess : token.colorError, // Use semantic tokens }} suffix={ { } > {isValid ? ( - + ) : ( - + )} } diff --git a/src/renderer/src/components/Settings/Settings.PaintScaleOutputPaths.tsx b/src/renderer/src/components/Settings/Settings.PaintScaleOutputPaths.tsx index d3f695d..56436d2 100644 --- a/src/renderer/src/components/Settings/Settings.PaintScaleOutputPaths.tsx +++ b/src/renderer/src/components/Settings/Settings.PaintScaleOutputPaths.tsx @@ -4,8 +4,18 @@ import { FolderOpenFilled, WarningFilled, } from "@ant-design/icons"; -import { Button, Card, Input, Modal, Select, Space, Table, Tag } from "antd"; -import { FC, useState } from "react"; +import { + Button, + Card, + Input, + Modal, + Select, + Space, + Table, + Tag, + theme, +} from "antd"; +import { JSX, useState } from "react"; import { useTranslation } from "react-i18next"; import { PaintScaleConfig, @@ -14,7 +24,8 @@ import { } from "../../../../util/types/paintScale"; import { usePaintScaleConfig } from "./PaintScale/usePaintScaleConfig"; -const SettingsPaintScaleOutputPaths: FC = () => { +const SettingsPaintScaleOutputPaths = (): JSX.Element => { + const { token } = theme.useToken(); const { t } = useTranslation(); const { paintScaleConfigs, @@ -77,13 +88,13 @@ const SettingsPaintScaleOutputPaths: FC = () => { placeholder={t("settings.labels.paintScalePath")} disabled style={{ - borderColor: isValid ? "#52c41a" : "#d9d9d9", + borderColor: isValid ? token.colorSuccess : token.colorError, }} suffix={ isValid ? ( - + ) : ( - + ) } />