feature/IO-3725-RPS-Changes - Make settings look nicer

This commit is contained in:
Dave
2026-05-28 12:03:15 -04:00
parent 972b399ba8
commit 562adaa795
16 changed files with 390 additions and 67 deletions

1
.gitignore vendored
View File

@@ -116,3 +116,4 @@ deploy.ps1
macbuild.sh
Usage.md
dist-electron
.idea

View File

@@ -10,7 +10,7 @@ In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
Open [http://localhost:3006](http://localhost:3006) to view it in the browser.
The page will reload if you make edits.<br />
You will also see any lint errors in the console.

View File

@@ -174,7 +174,7 @@ function createWindow() {
// and load the index.html of the app.
// win.loadFile("index.html");
if (isDev) {
mainWindow.loadURL("http://localhost:3000");
mainWindow.loadURL("http://localhost:3006");
} else {
const indexPath = path.join(appPath, "build", "index.html");
mainWindow.loadFile(indexPath);

View File

@@ -14,7 +14,7 @@ migrations_directory: migrations
seeds_directory: seeds
actions:
kind: synchronous
handler_webhook_baseurl: http://localhost:3000
handler_webhook_baseurl: http://localhost:3006
codegen:
framework: ""
output_dir: ""

View File

@@ -14,7 +14,7 @@ migrations_directory: migrations
seeds_directory: seeds
actions:
kind: synchronous
handler_webhook_baseurl: http://localhost:3000
handler_webhook_baseurl: http://localhost:3006
codegen:
framework: ""
output_dir: ""

View File

@@ -1,4 +1,4 @@
import { Alert } from "antd";
import { Alert, Typography } from "antd";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -15,13 +15,17 @@ const mapStateToProps = createStructuredSelector({
});
export function WatcherStatusAtom({ watcherStatus, watcherError }) {
const statusClassName =
watcherStatus === "Started"
? "settings-watcher-status settings-watcher-status--started"
: "settings-watcher-status settings-watcher-status--stopped";
return (
<div
onDoubleClick={() => ipcRenderer.send("test")}
style={{ color: watcherStatus === "Started" ? "green" : "tomato" }}
>
<strong>{watcherStatus}</strong>
{watcherError && <Alert message={watcherError} />}
<div className="settings-watcher-status-wrap" onDoubleClick={() => ipcRenderer.send("test")}>
<div className={statusClassName}>
<Typography.Text strong>{watcherStatus || "Unknown"}</Typography.Text>
</div>
{watcherError && <Alert className="settings-watcher-status__error" message={watcherError} type="error" showIcon />}
</div>
);
}

View File

@@ -3,9 +3,13 @@ import React from "react";
import ipcTypes from "../../../ipc.types";
const { ipcRenderer } = window;
export default function FilepathAddMolecule() {
export default function FilepathAddMolecule(buttonProps) {
const handleClick = () => {
ipcRenderer.send(ipcTypes.fileWatcher.toMain.addPath);
};
return <Button onClick={handleClick}>Add Path</Button>;
return (
<Button {...buttonProps} onClick={handleClick}>
Add Path
</Button>
);
}

View File

@@ -1,21 +1,29 @@
import { Button, Form, Input, InputNumber, Popconfirm, Select, Space, Switch, Typography } from "antd";
import { Button, Flex, Form, Input, InputNumber, Popconfirm, Select, Switch, Typography } from "antd";
import React from "react";
import LayoutFormRow from "../../atoms/layout-form-row/layout-form-row.atom";
import "./shop-settings-form.molecule.styles.scss";
export default function ShopSettingsFormMolecule({ form, saveLoading }) {
return (
<Space direction="vertical">
<Typography.Title>Shop Settings</Typography.Title>
<Popconfirm
title="Changing these settings will require manually restarting RPS on all machines."
onConfirm={() => form.submit()}
>
<Button type="primary" loading={saveLoading}>
Save
</Button>
</Popconfirm>
<div className="shop-settings-form">
<Flex justify="space-between" align="flex-start" wrap gap="small" className="shop-settings-form__header">
<div>
<Typography.Title level={3}>Shop Settings</Typography.Title>
<Typography.Paragraph type="secondary">
Update the defaults RPS uses when calculating and syncing shop data.
</Typography.Paragraph>
</div>
<Popconfirm
title="Changing these settings will require manually restarting RPS on all machines."
onConfirm={() => form.submit()}
>
<Button type="primary" size="small" loading={saveLoading}>
Save changes
</Button>
</Popconfirm>
</Flex>
<Space wrap>
<LayoutFormRow className="shop-settings-form__row" grow>
<Form.Item
label="Shop Name"
name="shopname"
@@ -38,11 +46,13 @@ export default function ShopSettingsFormMolecule({ form, saveLoading }) {
}
]}
>
<Select mode="tags" />
<Select mode="tags" style={{ width: "100%" }} />
</Form.Item>
<Form.Item name="ins_rule_set" label="Insurance Rule Set">
<Input disabled />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow className="shop-settings-form__row" grow>
<Form.Item
label="Alert when Parts Price Difference Less Than"
name="ppd_diff_alert"
@@ -52,7 +62,7 @@ export default function ShopSettingsFormMolecule({ form, saveLoading }) {
}
]}
>
<InputNumber />
<InputNumber style={{ width: "100%" }} />
</Form.Item>
<Form.Item
label="Calculate using Part Quantity"
@@ -62,7 +72,7 @@ export default function ShopSettingsFormMolecule({ form, saveLoading }) {
>
<Switch />
</Form.Item>
</Space>
</Space>
</LayoutFormRow>
</div>
);
}

View File

@@ -0,0 +1,42 @@
.shop-settings-form {
display: flex;
flex-direction: column;
gap: 16px;
}
.shop-settings-form__header {
width: 100%;
}
.shop-settings-form__header .ant-typography {
margin-bottom: 0;
}
.shop-settings-form__header .ant-typography + .ant-typography {
margin-top: 4px;
max-width: 640px;
}
.shop-settings-form__row .ant-form-item {
margin-bottom: 12px;
}
.shop-settings-form__row .ant-form-item-label > label {
white-space: normal;
line-height: 1.3;
}
.shop-settings-form__row .ant-input-number,
.shop-settings-form__row .ant-select {
width: 100%;
}
@media (max-width: 768px) {
.shop-settings-form__header {
align-items: stretch !important;
}
.shop-settings-form__header .ant-btn {
width: 100%;
}
}

View File

@@ -3,14 +3,10 @@ import React from "react";
import ipcTypes from "../../../ipc.types";
const { ipcRenderer } = window;
export default function WatcherStartMolecule() {
export default function WatcherStartMolecule(buttonProps) {
const handleClick = () => {
ipcRenderer.send(ipcTypes.fileWatcher.toMain.start);
};
return (
<div>
<Button onClick={handleClick}>Start Watcher</Button>
</div>
);
return <Button {...buttonProps} onClick={handleClick}>Start Watcher</Button>;
}

View File

@@ -3,14 +3,10 @@ import React from "react";
import ipcTypes from "../../../ipc.types";
const { ipcRenderer } = window;
export default function WatcherStopMolecule() {
export default function WatcherStopMolecule(buttonProps) {
const handleClick = () => {
ipcRenderer.send(ipcTypes.fileWatcher.toMain.stop);
};
return (
<div>
<Button onClick={handleClick}>Stop Watcher</Button>
</div>
);
return <Button {...buttonProps} onClick={handleClick}>Stop Watcher</Button>;
}

View File

@@ -1,4 +1,4 @@
import { List, Typography } from "antd";
import { Empty, Flex, List, Typography } from "antd";
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -22,10 +22,32 @@ export function FilePathsList({ watchedPaths }) {
}, []);
return (
<div>
<Typography.Title>Watcher File Paths</Typography.Title>
<List dataSource={watchedPaths || []} renderItem={FilepathItemMolecule} />
<FilepathAddMolecule />
<div className="settings-filepaths">
<Flex align="center" justify="space-between" gap="small" wrap className="settings-filepaths__header">
<div>
<Typography.Title level={3}>Watcher File Paths</Typography.Title>
<Typography.Text type="secondary">
{watchedPaths && watchedPaths.length
? `${watchedPaths.length} path${watchedPaths.length === 1 ? "" : "s"} being watched`
: "Choose the folders the watcher should monitor."}
</Typography.Text>
</div>
<FilepathAddMolecule size="small" type="primary" />
</Flex>
<List
className="settings-filepaths__list"
size="small"
dataSource={watchedPaths || []}
renderItem={FilepathItemMolecule}
locale={{
emptyText: (
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description="No watched folders yet"
/>
),
}}
/>
</div>
);
}

View File

@@ -1,16 +1,48 @@
import { Typography } from "antd";
import { Button, Flex, Typography } from "antd";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import ipcTypes from "../../../ipc.types";
import { selectWatcherStatus } from "../../../redux/application/application.selectors";
import WatcherStatusAtom from "../../atoms/watcher-status/watcher-status.atom";
import WatcherStartMolecule from "../../molecules/watcher-start/watcher-start.molecule";
import WatcherStopMolecule from "../../molecules/watcher-stop/watcher-stop.molecule";
const { ipcRenderer } = window;
const mapStateToProps = createStructuredSelector({
watcherStatus: selectWatcherStatus,
});
export function WatcherManagerOrganism({ watcherStatus }) {
const watcherStarted = watcherStatus === "Started";
const handleWatcherToggle = () => {
ipcRenderer.send(
watcherStarted
? ipcTypes.fileWatcher.toMain.stop
: ipcTypes.fileWatcher.toMain.start
);
};
export default function WatcherManagerOrganism() {
return (
<div>
<Typography.Title level={4}>Watcher Status</Typography.Title>
<WatcherStatusAtom />
<WatcherStartMolecule />
<WatcherStopMolecule />
<div className="settings-watcher-manager">
<div className="settings-watcher-manager__header">
<Typography.Title level={5}>Watcher Status</Typography.Title>
<Typography.Text type="secondary">
Start or stop the watcher without leaving settings.
</Typography.Text>
</div>
<Flex align="center" justify="space-between" gap="small" wrap className="settings-watcher-manager__content">
<WatcherStatusAtom />
<Button
size="small"
type={watcherStarted ? "default" : "primary"}
danger={watcherStarted}
className="settings-watcher-manager__toggle"
onClick={handleWatcherToggle}
>
{watcherStarted ? "Stop Watcher" : "Start Watcher"}
</Button>
</Flex>
</div>
);
}
export default connect(mapStateToProps)(WatcherManagerOrganism);

View File

@@ -1,4 +1,4 @@
import { Col, Row } from "antd";
import { Card, Col, Row, Space, Typography } from "antd";
import React, { useEffect } from "react";
import ipcTypes from "../../../ipc.types";
import NotificationsToggleAtom from "../../atoms/notifications-toggle/notifications-toggle.atom";
@@ -7,6 +7,7 @@ import WatcherPollingMolecule from "../../molecules/watcher-polling/watcher-poll
import FilePathsListOrganism from "../../organisms/filepaths-list/filepaths-list.organism";
import ShopSettingsOrganism from "../../organisms/shop-settings/shop-settings.organism";
import WatcherManagerOrganism from "../../organisms/watcher-manager/watcher-manager.organism";
import "./settings.page.styles.scss";
const { ipcRenderer } = window;
export default function SettingsPage() {
@@ -15,20 +16,43 @@ export default function SettingsPage() {
}, []);
return (
<div>
<Row gutter={[16, 16]}>
<Col span={18}>
<FilePathsListOrganism />
<div className="settings-page-container">
<div className="settings-page__hero">
<Typography.Title level={3}>Settings</Typography.Title>
<Typography.Paragraph type="secondary">
Manage watcher behavior, notifications, and shop defaults in one compact workspace.
</Typography.Paragraph>
</div>
<Row gutter={[16, 16]} align="top">
<Col xs={24} xl={16}>
<Card variant="borderless" className="settings-page__card settings-page__card--filepaths">
<FilePathsListOrganism />
</Card>
</Col>
<Col span={6}>
<WatcherManagerOrganism />
<WatcherPollingMolecule />
<NotificationsToggleAtom />
<WatcherStartupAtom />
<Col xs={24} xl={8}>
<Space direction="vertical" size={16} className="settings-page__side-stack">
<Card variant="borderless" className="settings-page__card">
<WatcherManagerOrganism />
</Card>
<Card variant="borderless" className="settings-page__card settings-page__card--automation">
<Typography.Title level={5}>Automation</Typography.Title>
<Typography.Paragraph type="secondary">
Fine-tune polling, startup behavior, and desktop notifications.
</Typography.Paragraph>
<Space direction="vertical" size={10} className="settings-page__controls">
<WatcherPollingMolecule />
<NotificationsToggleAtom />
<WatcherStartupAtom />
</Space>
</Card>
</Space>
</Col>
</Row>
<ShopSettingsOrganism />
<Card variant="borderless" className="settings-page__card settings-page__card--shop">
<ShopSettingsOrganism />
</Card>
</div>
);
}

View File

@@ -0,0 +1,192 @@
.settings-page-container {
height: 100%;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 16px;
padding-bottom: 16px;
}
.settings-page__hero {
padding: 18px 24px;
border-radius: 18px;
background: linear-gradient(135deg, #ffffff 0%, #f5f9ff 55%, #eef4ff 100%);
border: 1px solid #dbe7ff;
box-shadow: 0 10px 24px rgba(23, 43, 77, 0.06);
.ant-typography {
margin-bottom: 0;
}
.ant-typography + .ant-typography {
margin-top: 4px;
}
}
.settings-page__side-stack {
width: 100%;
}
.settings-page__side-stack > .ant-space-item,
.settings-page__controls > .ant-space-item {
width: 100%;
}
.settings-page__card {
border-radius: 18px;
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06);
.ant-card-body {
padding: 18px 20px;
}
.ant-typography:last-child {
margin-bottom: 0;
}
}
.settings-page__card--filepaths {
.ant-card-body {
padding-bottom: 14px;
}
}
.settings-page__card--automation {
.ant-typography + .ant-typography {
margin-top: 2px;
}
}
.settings-page__controls {
width: 100%;
}
.settings-page__controls > .ant-space-item > * {
padding: 10px 12px;
border-radius: 12px;
background: #fafcff;
border: 1px solid #edf2ff;
}
.settings-page__controls > .ant-space-item > * > div {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.settings-page__controls > .ant-space-item > * > div + div {
margin-top: 8px;
}
.settings-page__controls > .ant-space-item > * > div > div:first-child {
flex: 1;
min-width: 0;
}
.settings-page__controls .ant-input-number {
width: 96px;
}
.settings-filepaths {
display: flex;
flex-direction: column;
gap: 14px;
}
.settings-filepaths__header {
width: 100%;
}
.settings-filepaths__header .ant-typography {
margin-bottom: 0;
}
.settings-filepaths__list {
overflow: hidden;
border: 1px solid #edf2ff;
border-radius: 14px;
background: #fafcff;
}
.settings-filepaths__list .ant-list-item {
padding: 12px 16px;
}
.settings-filepaths__list .ant-list-empty-text {
padding: 28px 16px;
}
.settings-filepaths__list .ant-empty {
margin-block: 8px;
}
.settings-watcher-manager {
display: flex;
flex-direction: column;
gap: 12px;
}
.settings-watcher-manager__header .ant-typography {
margin-bottom: 0;
}
.settings-watcher-manager__content {
width: 100%;
}
.settings-watcher-manager__toggle {
min-width: 112px;
}
.settings-watcher-status-wrap {
display: flex;
flex-direction: column;
gap: 10px;
}
.settings-watcher-status {
display: inline-flex;
align-items: center;
padding: 6px 12px;
border-radius: 999px;
border: 1px solid transparent;
}
.settings-watcher-status--started {
color: #389e0d;
background: #f6ffed;
border-color: #b7eb8f;
}
.settings-watcher-status--stopped {
color: #cf1322;
background: #fff1f0;
border-color: #ffa39e;
}
.settings-watcher-status__error {
max-width: 100%;
}
@media (max-width: 768px) {
.settings-page-container {
gap: 16px;
padding-bottom: 16px;
}
.settings-page__hero,
.settings-page__card .ant-card-body {
padding: 20px;
}
.settings-filepaths__header,
.settings-watcher-manager__content {
align-items: stretch !important;
}
.settings-watcher-manager__toggle {
flex: 1;
}
}

View File

@@ -19,7 +19,7 @@ export default defineConfig({
},
server: {
host: true,
port: 3000,
port: 3006,
open: true
},
build: {