First Commit
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Paper,
|
||||
Select,
|
||||
Tab,
|
||||
Tabs,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
|
||||
import { JobTableColumns } from "./JobTable";
|
||||
import ImportExportJobs from "./ImportExportJobs";
|
||||
import GoogleAuthCard from "./GoogleAuthCard";
|
||||
import RulesSettingsCard from "./RulesSettingsCard";
|
||||
import BackupCard from "./BackupCard";
|
||||
import AuthStatusCard from "./AuthStatusCard";
|
||||
|
||||
export type ThemeModePref = "system" | "light" | "dark";
|
||||
|
||||
interface Props {
|
||||
pageSize: 15 | 20 | 25;
|
||||
onPageSizeChange: (n: 15 | 20 | 25) => void;
|
||||
columns: JobTableColumns;
|
||||
onColumnsChange: (next: JobTableColumns) => void;
|
||||
themeMode: ThemeModePref;
|
||||
onThemeModeChange: (v: ThemeModePref) => void;
|
||||
accentColor: string;
|
||||
onAccentColorChange: (v: string) => void;
|
||||
onResetAccentColor: () => void;
|
||||
}
|
||||
|
||||
function TabPanel({ value, index, children }: { value: number; index: number; children: React.ReactNode }) {
|
||||
if (value !== index) return null;
|
||||
return <Box sx={{ mt: 2 }}>{children}</Box>;
|
||||
}
|
||||
|
||||
const ACCENTS = ["#7c4dff", "#4f8cff", "#16a34a", "#f59e0b", "#e11d48", "#06b6d4"]; // violet, blue, green, amber, rose, cyan
|
||||
|
||||
export default function SettingsView({
|
||||
pageSize,
|
||||
onPageSizeChange,
|
||||
columns,
|
||||
onColumnsChange,
|
||||
themeMode,
|
||||
onThemeModeChange,
|
||||
accentColor,
|
||||
onAccentColorChange,
|
||||
onResetAccentColor,
|
||||
}: Props) {
|
||||
const [tab, setTab] = useState(0);
|
||||
|
||||
const accentOk = useMemo(() => /^#[0-9a-fA-F]{6}$/.test(accentColor), [accentColor]);
|
||||
|
||||
return (
|
||||
<Paper sx={{ mt: 0, p: 2 }}>
|
||||
<Typography variant="h5" sx={{ mb: 1, fontWeight: 900 }}>
|
||||
Settings
|
||||
</Typography>
|
||||
<Typography sx={{ color: "text.secondary", mb: 2 }}>
|
||||
Preferences and admin tools.
|
||||
</Typography>
|
||||
|
||||
<Tabs value={tab} onChange={(_, v) => setTab(v)} sx={{ mb: 1 }}>
|
||||
<Tab label="General" />
|
||||
<Tab label="Follow-ups" />
|
||||
<Tab label="Notifications" />
|
||||
<Tab label="Account" />
|
||||
<Tab label="Backup" />
|
||||
</Tabs>
|
||||
|
||||
<TabPanel value={tab} index={0}>
|
||||
<Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" }, gap: 2 }}>
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Typography sx={{ fontWeight: 950, mb: 1 }}>Appearance</Typography>
|
||||
|
||||
<FormControl fullWidth sx={{ mb: 2 }}>
|
||||
<InputLabel id="theme-mode-label">Theme</InputLabel>
|
||||
<Select
|
||||
labelId="theme-mode-label"
|
||||
value={themeMode}
|
||||
label="Theme"
|
||||
onChange={(e) => onThemeModeChange(e.target.value as ThemeModePref)}
|
||||
>
|
||||
<MenuItem value="system">System</MenuItem>
|
||||
<MenuItem value="dark">Dark</MenuItem>
|
||||
<MenuItem value="light">Light</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 2, flexWrap: "wrap" }}>
|
||||
<TextField
|
||||
label="Accent"
|
||||
type="color"
|
||||
value={accentOk ? accentColor : "#7c4dff"}
|
||||
onChange={(e) => onAccentColorChange(e.target.value)}
|
||||
sx={{ width: 160 }}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
/>
|
||||
<Button variant="outlined" onClick={onResetAccentColor}>
|
||||
Reset
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap", mt: 2 }}>
|
||||
{ACCENTS.map((c) => (
|
||||
<button
|
||||
key={c}
|
||||
type="button"
|
||||
onClick={() => onAccentColorChange(c)}
|
||||
title={c}
|
||||
style={{
|
||||
width: 28,
|
||||
height: 28,
|
||||
borderRadius: 999,
|
||||
border: c.toLowerCase() === accentColor.toLowerCase() ? "2px solid rgba(255,255,255,0.85)" : "1px solid rgba(148,163,184,0.35)",
|
||||
background: c,
|
||||
cursor: "pointer",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Typography variant="caption" sx={{ color: "text.secondary", display: "block", mt: 1 }}>
|
||||
Saved per user on this browser.
|
||||
</Typography>
|
||||
</Paper>
|
||||
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Typography sx={{ fontWeight: 950, mb: 1 }}>Jobs</Typography>
|
||||
|
||||
<Box sx={{ display: "flex", gap: 3, flexWrap: "wrap" }}>
|
||||
<Box sx={{ minWidth: 240 }}>
|
||||
<Typography variant="h6" sx={{ mb: 1 }}>
|
||||
Pagination
|
||||
</Typography>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="page-size-label">Rows per page</InputLabel>
|
||||
<Select
|
||||
labelId="page-size-label"
|
||||
value={pageSize}
|
||||
label="Rows per page"
|
||||
onChange={(e) => onPageSizeChange(e.target.value as 15 | 20 | 25)}
|
||||
>
|
||||
<MenuItem value={15}>15</MenuItem>
|
||||
<MenuItem value={20}>20</MenuItem>
|
||||
<MenuItem value={25}>25</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ minWidth: 240 }}>
|
||||
<Typography variant="h6" sx={{ mb: 1 }}>
|
||||
Columns
|
||||
</Typography>
|
||||
{(
|
||||
[
|
||||
["status", "Status"],
|
||||
["dateApplied", "Date applied"],
|
||||
["daysSince", "Days"],
|
||||
["jobUrl", "Job URL"],
|
||||
] as const
|
||||
).map(([key, label]) => (
|
||||
<FormControlLabel
|
||||
key={key}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={columns[key]}
|
||||
onChange={() => onColumnsChange({ ...columns, [key]: !columns[key] })}
|
||||
/>
|
||||
}
|
||||
label={label}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<ImportExportJobs />
|
||||
</Paper>
|
||||
</Box>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={tab} index={1}>
|
||||
<RulesSettingsCard />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={tab} index={2}>
|
||||
<Paper sx={{ p: 2, mt: 2 }}>
|
||||
<Typography sx={{ fontWeight: 950, mb: 0.5 }}>Email notifications</Typography>
|
||||
<Typography sx={{ color: "text.secondary" }}>
|
||||
Notifications are sent via SMTP (Gmail works). Configure SMTP in the API (`Email:*` settings or env vars like `EMAIL_SMTP_HOST`).
|
||||
</Typography>
|
||||
</Paper>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={tab} index={3}>
|
||||
<AuthStatusCard />
|
||||
<GoogleAuthCard />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={tab} index={4}>
|
||||
<BackupCard />
|
||||
</TabPanel>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user