feature/IO-3725-RPS-Changes - Make settings look nicer
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -116,3 +116,4 @@ deploy.ps1
|
||||
macbuild.sh
|
||||
Usage.md
|
||||
dist-electron
|
||||
.idea
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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: ""
|
||||
|
||||
@@ -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: ""
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
192
src/components/pages/settings/settings.page.styles.scss
Normal file
192
src/components/pages/settings/settings.page.styles.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
host: true,
|
||||
port: 3000,
|
||||
port: 3006,
|
||||
open: true
|
||||
},
|
||||
build: {
|
||||
|
||||
Reference in New Issue
Block a user