Fix dark theme fill, theme toggle visibility and bank account creation
- The Carbon theme class is now also set on html, with a themed background and a full-height root, so the dark theme fills the whole window instead of stopping at the content height. An inline script in index.html applies the saved theme before first paint. - The header theme button gets a tooltip, inline padding and a max width so it is no longer clipped at the window edge. New Invoice also gets a labelled Dark mode toggle wired to the same handler. - A shared BankAccountModal replaces the inline editor in Settings and adds a Create bank account button under the bank selector on New Invoice, which selects the new account on save. Settings keeps one Add bank account button under the table. Bank name and account number are now required.
This commit is contained in:
+34
-110
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState, type MouseEvent } from "react";
|
||||
import {
|
||||
Button,
|
||||
Column,
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
type Settings,
|
||||
type TaxType,
|
||||
} from "../lib/types";
|
||||
import { emailError, ifscError, panError, phoneError, vendorGstinError } from "../lib/validators";
|
||||
import { emailError, panError, phoneError, vendorGstinError } from "../lib/validators";
|
||||
import { ImagePicker } from "../components/ImagePicker";
|
||||
import { LogoBranding } from "../components/LogoBranding";
|
||||
import DataBackupPanel from "../components/DataBackupPanel";
|
||||
@@ -45,19 +45,9 @@ import ErpnextSettingsPanel from "../components/ErpnextSettingsPanel";
|
||||
import { FontOverrideTable } from "../components/FontOverrideTable";
|
||||
import { useToast } from "../components/ToastProvider";
|
||||
import { SETTINGS_TABS, loadSettingsTab, saveSettingsTab } from "../lib/settingsTab";
|
||||
import BankAccountModal from "../components/BankAccountModal";
|
||||
import SeriesSettings from "./SeriesSettings";
|
||||
|
||||
const blankBank = (): BankAccount => ({
|
||||
id: null,
|
||||
label: "",
|
||||
bankName: "",
|
||||
accountName: "",
|
||||
accountNo: "",
|
||||
branch: "",
|
||||
ifsc: "",
|
||||
isDefault: false,
|
||||
});
|
||||
|
||||
export default function AppSettings({
|
||||
settings,
|
||||
onSaved,
|
||||
@@ -68,7 +58,9 @@ export default function AppSettings({
|
||||
const [form, setForm] = useState<Settings>({ ...settings });
|
||||
const [diagnosticsOpen, setDiagnosticsOpen] = useState(false);
|
||||
const [banks, setBanks] = useState<BankAccount[]>([]);
|
||||
const [bankModalOpen, setBankModalOpen] = useState(false);
|
||||
const [editing, setEditing] = useState<BankAccount | null>(null);
|
||||
const bankLauncher = useRef<HTMLElement | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const toast = useToast();
|
||||
// One form state for every tab and one Save button, so edits survive switching tabs.
|
||||
@@ -94,9 +86,6 @@ export default function AppSettings({
|
||||
const set = <K extends keyof Settings>(key: K, value: Settings[K]) =>
|
||||
setForm((prev) => ({ ...prev, [key]: value }));
|
||||
|
||||
const setBank = <K extends keyof BankAccount>(key: K, value: BankAccount[K]) =>
|
||||
setEditing((prev) => (prev ? { ...prev, [key]: value } : prev));
|
||||
|
||||
const registered = form.gstRegistration !== "unregistered";
|
||||
const gstinProblem = vendorGstinError(
|
||||
form.gstRegistration,
|
||||
@@ -123,22 +112,19 @@ export default function AppSettings({
|
||||
}
|
||||
};
|
||||
|
||||
const saveBank = async () => {
|
||||
if (!editing) return;
|
||||
setBusy(true);
|
||||
try {
|
||||
const saved = await api.saveBank(editing);
|
||||
if (saved.isDefault || form.defaultBankId === null) {
|
||||
setForm((prev) => ({ ...prev, defaultBankId: saved.id }));
|
||||
}
|
||||
setEditing(null);
|
||||
await loadBanks();
|
||||
toast.success("Bank account saved", "Click Save settings to keep a changed default account.");
|
||||
} catch (e) {
|
||||
toast.error("Could not save bank account", String(e));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
const openBankEditor = (bank: BankAccount | null, trigger: HTMLElement) => {
|
||||
bankLauncher.current = trigger;
|
||||
setEditing(bank);
|
||||
setBankModalOpen(true);
|
||||
};
|
||||
|
||||
const onBankSaved = (saved: BankAccount) => {
|
||||
if (saved.isDefault || form.defaultBankId === null) {
|
||||
setForm((prev) => ({ ...prev, defaultBankId: saved.id }));
|
||||
}
|
||||
setBankModalOpen(false);
|
||||
void loadBanks();
|
||||
toast.info("Click Save settings to keep a changed default account.");
|
||||
};
|
||||
|
||||
const removeBank = async (bank: BankAccount) => {
|
||||
@@ -306,12 +292,7 @@ export default function AppSettings({
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<section className="voiced-stack" style={{ marginBottom: "2rem" }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
<h3>Bank accounts</h3>
|
||||
<Button kind="tertiary" size="sm" renderIcon={Add} onClick={() => setEditing(blankBank())}>
|
||||
Add account
|
||||
</Button>
|
||||
</div>
|
||||
<h3>Bank accounts</h3>
|
||||
|
||||
{banks.length > 0 ? (
|
||||
<TableContainer>
|
||||
@@ -336,7 +317,7 @@ export default function AppSettings({
|
||||
<TableCell>{bank.isDefault ? "Yes" : "No"}</TableCell>
|
||||
<TableCell>
|
||||
<div className="voiced-header-action">
|
||||
<Button kind="ghost" size="sm" onClick={() => setEditing({ ...bank })}>
|
||||
<Button kind="ghost" size="sm" onClick={(e: MouseEvent<HTMLElement>) => openBankEditor({ ...bank }, e.currentTarget)}>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
@@ -358,78 +339,21 @@ export default function AppSettings({
|
||||
<div className="voiced-empty">No bank accounts yet.</div>
|
||||
)}
|
||||
|
||||
{editing ? (
|
||||
<div style={{ border: "1px solid var(--cds-border-subtle-01)", padding: "1rem" }}>
|
||||
<Grid narrow>
|
||||
<Column sm={4} md={4} lg={6}>
|
||||
<TextInput
|
||||
id="b-label"
|
||||
labelText="Label"
|
||||
value={editing.label}
|
||||
onChange={(e) => setBank("label", e.target.value)}
|
||||
/>
|
||||
</Column>
|
||||
<Column sm={4} md={4} lg={6}>
|
||||
<TextInput
|
||||
id="b-bankName"
|
||||
labelText="Bank name"
|
||||
value={editing.bankName}
|
||||
onChange={(e) => setBank("bankName", e.target.value)}
|
||||
/>
|
||||
</Column>
|
||||
<Column sm={4} md={4} lg={6}>
|
||||
<TextInput
|
||||
id="b-accountName"
|
||||
labelText="Account name"
|
||||
value={editing.accountName}
|
||||
onChange={(e) => setBank("accountName", e.target.value)}
|
||||
/>
|
||||
</Column>
|
||||
<Column sm={4} md={4} lg={6}>
|
||||
<TextInput
|
||||
id="b-accountNo"
|
||||
labelText="Account number"
|
||||
value={editing.accountNo}
|
||||
onChange={(e) => setBank("accountNo", e.target.value)}
|
||||
/>
|
||||
</Column>
|
||||
<Column sm={4} md={4} lg={6}>
|
||||
<TextInput
|
||||
id="b-branch"
|
||||
labelText="Branch"
|
||||
value={editing.branch}
|
||||
onChange={(e) => setBank("branch", e.target.value)}
|
||||
/>
|
||||
</Column>
|
||||
<Column sm={4} md={4} lg={6}>
|
||||
<TextInput
|
||||
id="b-ifsc"
|
||||
labelText="IFSC"
|
||||
value={editing.ifsc}
|
||||
onChange={(e) => setBank("ifsc", e.target.value.toUpperCase())}
|
||||
invalid={Boolean(ifscError(editing.ifsc))}
|
||||
invalidText={ifscError(editing.ifsc)}
|
||||
/>
|
||||
</Column>
|
||||
<Column sm={4} md={8} lg={16}>
|
||||
<Toggle
|
||||
id="b-default"
|
||||
labelText="Use as default account"
|
||||
toggled={editing.isDefault}
|
||||
onToggle={(value) => setBank("isDefault", value)}
|
||||
/>
|
||||
</Column>
|
||||
</Grid>
|
||||
<div className="voiced-header-action" style={{ marginTop: "1rem" }}>
|
||||
<Button size="sm" onClick={saveBank} disabled={busy}>
|
||||
{busy ? "Saving…" : "Save account"}
|
||||
</Button>
|
||||
<Button kind="ghost" size="sm" onClick={() => setEditing(null)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<Button
|
||||
kind="ghost"
|
||||
size="sm"
|
||||
renderIcon={Add}
|
||||
onClick={(e: MouseEvent<HTMLElement>) => openBankEditor(null, e.currentTarget)}
|
||||
>
|
||||
Add bank account
|
||||
</Button>
|
||||
<BankAccountModal
|
||||
open={bankModalOpen}
|
||||
bank={editing}
|
||||
launcherRef={bankLauncher}
|
||||
onClose={() => setBankModalOpen(false)}
|
||||
onSaved={onBankSaved}
|
||||
/>
|
||||
</section>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
|
||||
Reference in New Issue
Block a user