Add dark mode.

This commit is contained in:
Patrick Fic
2025-02-19 12:46:09 -08:00
parent 0cea35ba24
commit 47adf28563
13 changed files with 131 additions and 117 deletions

View File

@@ -1,5 +1,5 @@
import { ApolloProvider } from "@apollo/client";
import { ConfigProvider } from "antd";
import { ConfigProvider, theme } from "antd";
import enLocale from "antd/es/locale/en_US";
import React, { useEffect } from "react";
import { connect } from "react-redux";
@@ -11,18 +11,19 @@ import client from "../graphql/GraphQLClient";
import ipcTypes from "../ipc.types";
import "../ipc/ipc-renderer-handler";
import { checkUserSession } from "../redux/user/user.actions";
import { selectCurrentUser } from "../redux/user/user.selectors";
import { selectCurrentUser, selectDarkMode } from "../redux/user/user.selectors";
import "./App.styles.scss";
const { ipcRenderer } = window;
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
darkMode: selectDarkMode
});
const mapDispatchToProps = (dispatch) => ({
checkUserSession: () => dispatch(checkUserSession()),
checkUserSession: () => dispatch(checkUserSession())
});
export function App({ currentUser, checkUserSession }) {
export function App({ currentUser, checkUserSession, darkMode }) {
useEffect(() => {
checkUserSession();
}, [checkUserSession]);
@@ -42,6 +43,9 @@ export function App({ currentUser, checkUserSession }) {
<ApolloProvider client={client}>
<ConfigProvider
componentSize="small"
theme={{
algorithm: darkMode ? theme.darkAlgorithm : theme.defaultAlgorithm
}}
input={{ autoComplete: "new-password" }}
locale={enLocale}
>

View File

@@ -33,6 +33,7 @@ export function JobsListItemMolecule({ selectedJobId, setSelectedJobId, item, re
<div className="jobs-list-item-refresh-content">Refresh All</div>
</List.Item>
);
return (
<List.Item className="jobs-list-item" key={item.id} onClick={() => handleSelect(item.id)}>
<Card className={`jobs-list-item-content ${item.id === selectedJobId ? "jobs-list-item-content-selected" : ""}`}>

View File

@@ -1,7 +1,7 @@
.jobs-list-item {
padding: 0rem !important;
margin: 0;
border: 0.4rem solid #f0f0f0 !important;
padding: 0.4rem !important;
.jobs-list-item-content {
&-selected {
border-left: 3px solid #1890ff;
@@ -13,7 +13,7 @@
//padding: 0.5rem;
width: 100%;
}
background-color: #f0f0f0;
// background-color: #f0f0f0;
cursor: pointer;
// &:hover {
// background-color: #e6f7ff;

View File

@@ -1,7 +1,7 @@
.jobs-detail-container {
height: 100%;
overflow-y: auto;
background-color: rgb(244, 244, 244);
//background-color: rgb(244, 244, 244);
& > * {
margin: 0.7rem;
}

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import { List } from "antd";
import { Button, List } from "antd";
import React, { useState } from "react";
import InfiniteScroll from "react-infinite-scroller";
import { QUERY_ALL_JOBS_PAGINATED } from "../../../graphql/jobs.queries";
@@ -10,23 +10,20 @@ const limit = 20;
export default function JobsTableOrganism() {
const [state, setState] = useState({ hasMore: true });
const { loading, error, data, refetch, fetchMore } = useQuery(
QUERY_ALL_JOBS_PAGINATED,
{
variables: {
offset: 0,
limit: limit,
order: [{ updated_at: "desc" }],
},
const { loading, error, data, refetch, fetchMore } = useQuery(QUERY_ALL_JOBS_PAGINATED, {
variables: {
offset: 0,
limit: limit,
order: [{ updated_at: "desc" }]
}
);
});
const handleInfiniteOnLoad = async (page) => {
console.log("Fetching more records!", page, fetchMore);
if (fetchMore) {
await fetchMore({
variables: {
offset: limit * page,
offset: limit * page
},
updateQuery: (prev, { fetchMoreResult }) => {
@@ -37,19 +34,16 @@ export default function JobsTableOrganism() {
}
const newCache = Object.assign({}, prev, {
jobs: [...prev.jobs, ...fetchMoreResult.jobs],
jobs: [...prev.jobs, ...fetchMoreResult.jobs]
});
if (
newCache.jobs.length >=
(data && data.jobs_aggregate.aggregate.count)
) {
if (newCache.jobs.length >= (data && data.jobs_aggregate.aggregate.count)) {
console.log("No more results.");
setState({ ...state, hasMore: false });
}
return newCache;
},
}
});
// if (data.jobs.length >= data.jobs_aggregate.aggregate.count) {
// console.log("No more results.");
@@ -58,16 +52,13 @@ export default function JobsTableOrganism() {
}
};
if (error)
return (
<ErrorResultAtom
title="Error fetching Jobs data."
errorMessage={JSON.stringify(error)}
/>
);
if (error) return <ErrorResultAtom title="Error fetching Jobs data." errorMessage={JSON.stringify(error)} />;
return (
<div className="jobs-list-container">
<Button onClick={() => refetch()} style={{ marginBottom: ".5rem" }} size="large">
Refresh
</Button>
<div className="jobs-list-infinite-container">
<InfiniteScroll
pageStart={0}
@@ -79,17 +70,20 @@ export default function JobsTableOrganism() {
<List
bordered
loading={loading}
dataSource={data ? [{ isRefresh: true }, ...data.jobs] : []}
renderItem={(item) => (
<JobsListItemMolecule item={item} refetch={refetch} />
)}
dataSource={
data
? [
//{ isRefresh: true },
...data.jobs
]
: []
}
renderItem={(item) => <JobsListItemMolecule item={item} refetch={refetch} />}
></List>
</InfiniteScroll>
</div>
<div>
{`${data ? data.jobs.length : 0} jobs loaded. ${
data ? data.jobs_aggregate.aggregate.count : 0
} total jobs.`}
{`${data ? data.jobs.length : 0} jobs loaded. ${data ? data.jobs_aggregate.aggregate.count : 0} total jobs.`}
</div>
</div>
);

View File

@@ -4,24 +4,48 @@ import {
CloseOutlined,
FileAddFilled,
LogoutOutlined,
MoonOutlined,
PieChartOutlined,
SettingFilled,
AlertOutlined
SunOutlined
} from "@ant-design/icons";
import { Menu } from "antd";
import React from "react";
import { Link, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import ipcTypes from "../../../ipc.types";
import { selectDarkMode } from "../../../redux/user/user.selectors";
import SiderSignOut from "../../molecules/sider-sign-out/sider-sign-out.molecule";
import { connect } from "react-redux";
import { toggleDarkMode } from "../../../redux/user/user.actions";
const { ipcRenderer } = window;
export default function SiderMenuOrganism() {
const mapStateToProps = createStructuredSelector({
darkMode: selectDarkMode
});
const mapDispatchToProps = (dispatch) => ({
toggleDarkMode: () => dispatch(toggleDarkMode())
});
export function SiderMenuOrganism({ darkMode, toggleDarkMode }) {
const { pathname } = useLocation();
return (
<Menu
defaultSelectedKeys={[`/`]}
selectedKeys={[pathname]}
onClick={(e) => {
switch (e.key) {
case "darkmode":
toggleDarkMode();
break;
case "quit":
ipcRenderer.send(ipcTypes.quit);
break;
default:
break;
}
}}
mode="inline"
items={[
{
@@ -58,17 +82,13 @@ export default function SiderMenuOrganism() {
{
key: "quit",
icon: <CloseOutlined style={{ color: "tomato" }} />,
label: (
<span
onClick={() => {
ipcRenderer.send(ipcTypes.quit);
}}
>
Quit
</span>
)
label: "Quit"
},
{
key: "darkmode",
icon: darkMode ? <SunOutlined /> : <MoonOutlined />,
label: darkMode ? "Light Mode" : "Dark Mode"
}
// ...(process.env.NODE_ENV !== "production"
// ? [
// {
@@ -82,3 +102,5 @@ export default function SiderMenuOrganism() {
/>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(SiderMenuOrganism);

View File

@@ -1,7 +1,7 @@
.audit-container {
height: 100%;
overflow-y: auto;
background-color: rgb(244, 244, 244);
//background-color: rgb(244, 244, 244);
& > * {
padding: 1rem;
}

View File

@@ -1,7 +1,7 @@
.reporting-container {
height: 100%;
overflow-y: auto;
background-color: rgb(244, 244, 244);
//background-color: rgb(244, 244, 244);
& > .reporting-cards > * {
padding: 1rem;
}

View File

@@ -4,7 +4,7 @@ import { connect } from "react-redux";
import { Routes } from "react-router-dom";
import { Route } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../../redux/user/user.selectors";
import { selectBodyshop, selectDarkMode } from "../../../redux/user/user.selectors";
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
import ReleaseNotes from "../../molecules/release-notes/release-notes.molecule";
import NotificationModalOrganism from "../../organisms/notification-modal/notification-modal.organism";
@@ -17,10 +17,10 @@ import SettingsPage from "../settings/settings.page";
import AuditPage from "../audit/audit.page";
import AdminPage from "../admin/admin.page";
const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop });
const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, darkMode: selectDarkMode });
const mapDispatchToProps = (dispatch) => ({});
export function RoutesPage({ bodyshop }) {
export function RoutesPage({ bodyshop, darkMode }) {
if (bodyshop === false)
return (
<ErrorResultAtom
@@ -30,11 +30,11 @@ export function RoutesPage({ bodyshop }) {
);
return (
<Layout style={{ background: "#fff", height: "100vh" }} hasSider>
<Layout.Sider style={{ background: "#fff" }} collapsible defaultCollapsed="true">
<Layout style={{ height: "100vh" }} hasSider>
<Layout.Sider style={{ background: !darkMode && "#fff" }} collapsible defaultCollapsed="true">
<SiderMenuOrganism />
</Layout.Sider>
<Layout style={{ background: "#fff" }}>
<Layout style={{}}>
<Layout.Content style={{ marginLeft: "1rem", height: "100%" }}>
<NotificationModalOrganism />
<Routes>

View File

@@ -2,97 +2,101 @@ import UserActionTypes from "./user.types";
export const signInSuccess = (user) => ({
type: UserActionTypes.SIGN_IN_SUCCESS,
payload: user,
payload: user
});
export const signInFailure = (errorMsg) => ({
type: UserActionTypes.SIGN_IN_FAILURE,
payload: errorMsg,
payload: errorMsg
});
export const emailSignInStart = (emailAndPassword) => ({
type: UserActionTypes.EMAIL_SIGN_IN_START,
payload: emailAndPassword,
payload: emailAndPassword
});
export const checkUserSession = () => ({
type: UserActionTypes.CHECK_USER_SESSION,
type: UserActionTypes.CHECK_USER_SESSION
});
export const signOutStart = () => ({
type: UserActionTypes.SIGN_OUT_START,
type: UserActionTypes.SIGN_OUT_START
});
export const signOutSuccess = () => ({
type: UserActionTypes.SIGN_OUT_SUCCESS,
type: UserActionTypes.SIGN_OUT_SUCCESS
});
export const signOutFailure = (error) => ({
type: UserActionTypes.SIGN_OUT_FAILURE,
payload: error,
payload: error
});
export const unauthorizedUser = () => ({
type: UserActionTypes.UNAUTHORIZED_USER,
type: UserActionTypes.UNAUTHORIZED_USER
});
export const setUserLanguage = (language) => ({
type: UserActionTypes.SET_USER_LANGUAGE,
payload: language,
payload: language
});
export const updateUserDetails = (userDetails) => ({
type: UserActionTypes.UPDATE_USER_DETAILS,
payload: userDetails,
payload: userDetails
});
export const updateUserDetailsSuccess = (userDetails) => ({
type: UserActionTypes.UPDATE_USER_DETAILS_SUCCESS,
payload: userDetails,
payload: userDetails
});
export const setBodyshop = (bodyshop) => ({
type: UserActionTypes.SET_SHOP_DETAILS,
payload: bodyshop,
payload: bodyshop
});
export const setTargets = (targets) => ({
type: UserActionTypes.SET_TARGETS,
payload: targets,
payload: targets
});
export const setLocalFingerprint = (fingerprint) => ({
type: UserActionTypes.SET_LOCAL_FINGERPRINT,
payload: fingerprint,
payload: fingerprint
});
export const sendPasswordReset = (email) => ({
type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_START,
payload: email,
payload: email
});
export const sendPasswordResetFailure = (error) => ({
type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_FAILURE,
payload: error,
payload: error
});
export const sendPasswordResetSuccess = () => ({
type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_SUCCESS,
type: UserActionTypes.SEND_PASSWORD_RESET_EMAIL_SUCCESS
});
export const validatePasswordResetStart = (emailAndPin) => ({
type: UserActionTypes.VALIDATE_PASSWORD_RESET_START,
payload: emailAndPin,
payload: emailAndPin
});
export const validatePasswordResetSuccess = () => ({
type: UserActionTypes.VALIDATE_PASSWORD_RESET_SUCCESS,
type: UserActionTypes.VALIDATE_PASSWORD_RESET_SUCCESS
});
export const validatePasswordResetFailure = (error) => ({
type: UserActionTypes.VALIDATE_PASSWORD_RESET_FAILURE,
payload: error,
payload: error
});
export const setNotification = (notificationObject) => ({
type: UserActionTypes.SET_NOTIFICATIONS,
payload: notificationObject,
payload: notificationObject
});
export const checkForNotification = () => ({
type: UserActionTypes.CHECK_FOR_NOTIFICATION,
type: UserActionTypes.CHECK_FOR_NOTIFICATION
});
export const toggleDarkMode = () => ({
type: UserActionTypes.TOGGLE_DARK_MODE
});

View File

@@ -2,7 +2,7 @@ import UserActionTypes from "./user.types";
const INITIAL_STATE = {
currentUser: {
authorized: null,
authorized: null
//language: "en-US"
},
bodyshop: null,
@@ -13,10 +13,11 @@ const INITIAL_STATE = {
passwordreset: {
email: null,
error: null,
success: false,
success: false
},
loginLoading: false,
notifications: null,
darkMode: false
};
const userReducer = (state = INITIAL_STATE, action) => {
@@ -37,8 +38,8 @@ const userReducer = (state = INITIAL_STATE, action) => {
passwordreset: {
email: action.payload,
error: null,
success: false,
},
success: false
}
};
case UserActionTypes.VALIDATE_PASSWORD_RESET_FAILURE:
case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_FAILURE:
@@ -47,48 +48,52 @@ const userReducer = (state = INITIAL_STATE, action) => {
case UserActionTypes.SEND_PASSWORD_RESET_EMAIL_SUCCESS:
return {
...state,
passwordreset: { ...state.passwordreset, success: true },
passwordreset: { ...state.passwordreset, success: true }
};
case UserActionTypes.SIGN_IN_SUCCESS:
return {
...state,
currentUser: action.payload,
loginLoading: false,
error: null,
error: null
};
case UserActionTypes.SIGN_OUT_SUCCESS:
return {
...state,
currentUser: { authorized: false },
error: null,
error: null
};
case UserActionTypes.UNAUTHORIZED_USER:
return {
...state,
error: null,
currentUser: { authorized: false },
currentUser: { authorized: false }
};
case UserActionTypes.SET_USER_LANGUAGE:
return {
...state,
language: action.payload,
language: action.payload
};
case UserActionTypes.UPDATE_USER_DETAILS_SUCCESS:
return {
...state,
currentUser: {
...state.currentUser,
...action.payload, //Spread current user details in.
},
...action.payload //Spread current user details in.
}
};
case UserActionTypes.TOGGLE_DARK_MODE:
return {
...state,
darkMode: !state.darkMode
};
case UserActionTypes.SIGN_IN_FAILURE:
case UserActionTypes.SIGN_OUT_FAILURE:
case UserActionTypes.EMAIL_SIGN_UP_FAILURE:
return {
...state,
error: action.payload,
loginLoading: false,
loginLoading: false
};
case UserActionTypes.EMAIL_SIGN_IN_START:
return { ...state, loginLoading: true };

View File

@@ -2,31 +2,14 @@ import { createSelector } from "reselect";
const selectUser = (state) => state.user;
export const selectCurrentUser = createSelector(
[selectUser],
(user) => user.currentUser
);
export const selectCurrentUser = createSelector([selectUser], (user) => user.currentUser);
export const selectSignInError = createSelector(
[selectUser],
(user) => user.error
);
export const selectSignInError = createSelector([selectUser], (user) => user.error);
export const selectPasswordReset = createSelector(
[selectUser],
(user) => user.passwordreset
);
export const selectPasswordReset = createSelector([selectUser], (user) => user.passwordreset);
export const selectBodyshop = createSelector(
[selectUser],
(user) => user.bodyshop
);
export const selectBodyshop = createSelector([selectUser], (user) => user.bodyshop);
export const selectLoginLoading = createSelector(
[selectUser],
(user) => user.loginLoading
);
export const selectNotifications = createSelector(
[selectUser],
(user) => user.notifications
);
export const selectLoginLoading = createSelector([selectUser], (user) => user.loginLoading);
export const selectNotifications = createSelector([selectUser], (user) => user.notifications);
export const selectDarkMode = createSelector([selectUser], (user) => user.darkMode);

View File

@@ -30,5 +30,6 @@ const UserActionTypes = {
CHECK_FOR_NOTIFICATION: "CHECK_FOR_NOTIFICATION",
SET_NOTIFICATIONS: "SET_NOTIFICATIONS",
SET_TARGETS: "SET_TARGETS",
TOGGLE_DARK_MODE: "TOGGLE_DARK_MODE"
};
export default UserActionTypes;