Improve admin UI visual design — polish look and feel #59

Merged
ghost merged 4 commits from feat/ui-visual-polish into main 2026-03-19 03:33:35 +00:00
11 changed files with 183 additions and 109 deletions
+6 -6
View File
@@ -48,38 +48,38 @@ test("customer portal loads at root", async ({ page }) => {
test("admin appointments page loads", async ({ page }) => { test("admin appointments page loads", async ({ page }) => {
await page.goto("/admin"); await page.goto("/admin");
await expect(page.getByText("Groom Book")).toBeVisible(); await expect(page.getByText("GroomBook")).toBeVisible();
// Calendar/appointments view renders // Calendar/appointments view renders
await expect(page.locator("nav")).toBeVisible(); await expect(page.locator("nav")).toBeVisible();
}); });
test("admin clients page loads", async ({ page }) => { test("admin clients page loads", async ({ page }) => {
await page.goto("/admin/clients"); await page.goto("/admin/clients");
await expect(page.getByText("Groom Book")).toBeVisible(); await expect(page.getByText("GroomBook")).toBeVisible();
await expect(page.getByRole("link", { name: "Clients" })).toBeVisible(); await expect(page.getByRole("link", { name: "Clients" })).toBeVisible();
}); });
test("admin services page loads", async ({ page }) => { test("admin services page loads", async ({ page }) => {
await page.goto("/admin/services"); await page.goto("/admin/services");
await expect(page.getByText("Groom Book")).toBeVisible(); await expect(page.getByText("GroomBook")).toBeVisible();
await expect(page.getByRole("link", { name: "Services" })).toBeVisible(); await expect(page.getByRole("link", { name: "Services" })).toBeVisible();
}); });
test("admin staff page loads", async ({ page }) => { test("admin staff page loads", async ({ page }) => {
await page.goto("/admin/staff"); await page.goto("/admin/staff");
await expect(page.getByText("Groom Book")).toBeVisible(); await expect(page.getByText("GroomBook")).toBeVisible();
await expect(page.getByRole("link", { name: "Staff" })).toBeVisible(); await expect(page.getByRole("link", { name: "Staff" })).toBeVisible();
}); });
test("admin invoices page loads", async ({ page }) => { test("admin invoices page loads", async ({ page }) => {
await page.goto("/admin/invoices"); await page.goto("/admin/invoices");
await expect(page.getByText("Groom Book")).toBeVisible(); await expect(page.getByText("GroomBook")).toBeVisible();
await expect(page.getByRole("link", { name: "Invoices" })).toBeVisible(); await expect(page.getByRole("link", { name: "Invoices" })).toBeVisible();
}); });
test("admin reports page loads", async ({ page }) => { test("admin reports page loads", async ({ page }) => {
await page.goto("/admin/reports"); await page.goto("/admin/reports");
await expect(page.getByText("Groom Book")).toBeVisible(); await expect(page.getByText("GroomBook")).toBeVisible();
await expect(page.getByRole("link", { name: "Reports" })).toBeVisible(); await expect(page.getByRole("link", { name: "Reports" })).toBeVisible();
}); });
+27 -14
View File
@@ -23,29 +23,42 @@ const NAV_LINKS = [
function AdminLayout() { function AdminLayout() {
const location = useLocation(); const location = useLocation();
return ( return (
<div style={{ minHeight: "100vh", fontFamily: "system-ui, sans-serif" }}> <div style={{ minHeight: "100vh", fontFamily: "system-ui, sans-serif", background: "#f0f2f5" }}>
<nav <nav
style={{ style={{
padding: "0.75rem 1rem", padding: "0 1.25rem",
height: 52,
borderBottom: "1px solid #e2e8f0", borderBottom: "1px solid #e2e8f0",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: "0.25rem", gap: "0.25rem",
background: "#fff", background: "#fff",
boxShadow: "0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04)",
position: "sticky",
top: 0,
zIndex: 50,
}} }}
> >
<strong style={{ marginRight: "1rem", fontSize: 16 }}>Groom Book</strong> <strong style={{
marginRight: "1.25rem",
fontSize: 17,
color: "#1a202c",
letterSpacing: "-0.02em",
}}>
<span style={{ color: "#4f8a6f" }}>Groom</span>Book
</strong>
<Link <Link
to="/admin/book" to="/admin/book"
style={{ style={{
padding: "0.35rem 0.75rem", padding: "0.4rem 0.85rem",
borderRadius: 4, borderRadius: 6,
textDecoration: "none", textDecoration: "none",
fontSize: 14, fontSize: 13,
fontWeight: 600, fontWeight: 600,
color: "#fff", color: "#fff",
background: "#4f8a6f", background: "linear-gradient(135deg, #4f8a6f, #3d7a5f)",
marginRight: "0.5rem", marginRight: "0.5rem",
boxShadow: "0 1px 2px rgba(79, 138, 111, 0.3)",
}} }}
> >
Book Book
@@ -60,13 +73,13 @@ function AdminLayout() {
key={to} key={to}
to={to} to={to}
style={{ style={{
padding: "0.35rem 0.75rem", padding: "0.4rem 0.75rem",
borderRadius: 4, borderRadius: 6,
textDecoration: "none", textDecoration: "none",
fontSize: 14, fontSize: 13,
fontWeight: active ? 600 : 400, fontWeight: active ? 600 : 500,
color: active ? "#1d4ed8" : "#374151", color: active ? "#2d6a4f" : "#4b5563",
background: active ? "#eff6ff" : "transparent", background: active ? "#ecfdf5" : "transparent",
}} }}
> >
{label} {label}
@@ -74,7 +87,7 @@ function AdminLayout() {
); );
})} })}
</nav> </nav>
<main style={{ padding: "1rem 1.5rem" }}> <main style={{ padding: "1.25rem 1.5rem" }}>
<Routes> <Routes>
<Route path="/" element={<AppointmentsPage />} /> <Route path="/" element={<AppointmentsPage />} />
<Route path="/clients" element={<ClientsPage />} /> <Route path="/clients" element={<ClientsPage />} />
+7 -3
View File
@@ -23,12 +23,14 @@ function renderApp(route = "/admin") {
describe("App navigation", () => { describe("App navigation", () => {
it("renders the Groom Book brand", () => { it("renders the Groom Book brand", () => {
const nav = renderApp(); const nav = renderApp();
expect(within(nav).getByText("Groom Book")).toBeInTheDocument(); expect(
within(nav).getByText((_, el) => el?.tagName === "STRONG" && /Groom\s*Book/.test(el.textContent ?? ""))
).toBeInTheDocument();
}); });
it("renders the Book CTA button", () => { it("renders the Book CTA button", () => {
const nav = renderApp(); const nav = renderApp();
expect(within(nav).getByText("Book")).toBeInTheDocument(); expect(within(nav).getByRole("link", { name: "Book" })).toBeInTheDocument();
}); });
it("renders all primary nav links", () => { it("renders all primary nav links", () => {
@@ -61,6 +63,8 @@ describe("App navigation", () => {
</MemoryRouter> </MemoryRouter>
); );
// Customer portal should render at root - no admin nav present // Customer portal should render at root - no admin nav present
expect(screen.queryByText("Groom Book")).not.toBeInTheDocument(); expect(
screen.queryByText((_, el) => el?.tagName === "STRONG" && /Groom\s*Book/.test(el.textContent ?? ""))
).not.toBeInTheDocument();
}); });
}); });
+48 -2
View File
@@ -10,11 +10,13 @@ body {
font-size: 16px; font-size: 16px;
line-height: 1.5; line-height: 1.5;
color: #1a202c; color: #1a202c;
background: #f7fafc; background: #f0f2f5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
} }
a { a {
color: #4f8a6f; color: #3d7a5f;
text-decoration: none; text-decoration: none;
} }
@@ -25,4 +27,48 @@ a:hover {
h1 { h1 {
font-size: 1.5rem; font-size: 1.5rem;
margin-top: 0; margin-top: 0;
letter-spacing: -0.01em;
}
h2, h3, h4 {
letter-spacing: -0.01em;
}
/* ─── Admin button polish ─── */
button {
transition: background-color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease;
}
button:active:not(:disabled) {
transform: translateY(0.5px);
}
/* ─── Admin input / select focus states ─── */
input:focus, select:focus, textarea:focus {
outline: none;
border-color: #4f8a6f;
box-shadow: 0 0 0 3px rgba(79, 138, 111, 0.12);
}
/* ─── Admin card-like containers (borders get subtle shadow) ─── */
[style*="border: 1px solid"] {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}
/* ─── Scrollbar polish ─── */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
} }
+11 -10
View File
@@ -291,7 +291,7 @@ export function AppointmentsPage() {
</button> </button>
<button <button
onClick={() => openNewForm()} onClick={() => openNewForm()}
style={{ ...btnStyle, backgroundColor: "#3b82f6", color: "#fff", marginLeft: "auto", borderColor: "#3b82f6" }} style={{ ...btnStyle, backgroundColor: "#4f8a6f", color: "#fff", marginLeft: "auto", borderColor: "#4f8a6f" }}
> >
+ New Appointment + New Appointment
</button> </button>
@@ -370,11 +370,11 @@ export function AppointmentsPage() {
{days.map((day, i) => { {days.map((day, i) => {
const isToday = formatDate(day) === formatDate(new Date()); const isToday = formatDate(day) === formatDate(new Date());
return ( return (
<div key={i} style={{ border: "1px solid #e2e8f0", borderRadius: 6, overflow: "hidden", minHeight: 180 }}> <div key={i} style={{ border: "1px solid #e5e7eb", borderRadius: 8, overflow: "hidden", minHeight: 180, background: "#fff", boxShadow: "0 1px 3px rgba(0, 0, 0, 0.04)" }}>
<div <div
style={{ style={{
padding: "0.35rem 0.5rem", padding: "0.4rem 0.6rem",
background: isToday ? "#3b82f6" : "#f8fafc", background: isToday ? "linear-gradient(135deg, #4f8a6f, #3d7a5f)" : "#f8fafc",
color: isToday ? "#fff" : "#374151", color: isToday ? "#fff" : "#374151",
fontWeight: 600, fontWeight: 600,
fontSize: 12, fontSize: 12,
@@ -594,7 +594,7 @@ export function AppointmentsPage() {
<button <button
type="submit" type="submit"
disabled={saving} disabled={saving}
style={{ ...btnStyle, backgroundColor: "#3b82f6", color: "#fff", borderColor: "#3b82f6" }} style={{ ...btnStyle, backgroundColor: "#4f8a6f", color: "#fff", borderColor: "#4f8a6f" }}
> >
{saving {saving
? "Saving…" ? "Saving…"
@@ -841,19 +841,20 @@ function Field({ label, children }: { label: string; children: React.ReactNode }
} }
const btnStyle: React.CSSProperties = { const btnStyle: React.CSSProperties = {
padding: "0.35rem 0.75rem", padding: "0.4rem 0.85rem",
border: "1px solid #d1d5db", border: "1px solid #d1d5db",
borderRadius: 4, borderRadius: 6,
background: "#f9fafb", background: "#fff",
cursor: "pointer", cursor: "pointer",
fontSize: 13, fontSize: 13,
fontWeight: 500,
}; };
const inputStyle: React.CSSProperties = { const inputStyle: React.CSSProperties = {
width: "100%", width: "100%",
padding: "0.4rem 0.5rem", padding: "0.45rem 0.6rem",
border: "1px solid #d1d5db", border: "1px solid #d1d5db",
borderRadius: 4, borderRadius: 6,
fontSize: 14, fontSize: 14,
boxSizing: "border-box", boxSizing: "border-box",
}; };
+6 -6
View File
@@ -315,7 +315,7 @@ export function ClientsPage() {
<h1 style={{ margin: 0, fontSize: 20 }}>Clients</h1> <h1 style={{ margin: 0, fontSize: 20 }}>Clients</h1>
<button <button
onClick={openNewClient} onClick={openNewClient}
style={{ ...btnStyle, backgroundColor: "#3b82f6", color: "#fff", borderColor: "#3b82f6", marginLeft: "auto", padding: "0.25rem 0.6rem" }} style={{ ...btnStyle, backgroundColor: "#4f8a6f", color: "#fff", borderColor: "#4f8a6f", marginLeft: "auto", padding: "0.3rem 0.7rem" }}
> >
+ New + New
</button> </button>
@@ -387,7 +387,7 @@ export function ClientsPage() {
) : ( ) : (
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))", gap: "0.75rem" }}> <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))", gap: "0.75rem" }}>
{pets.map((p) => ( {pets.map((p) => (
<div key={p.id} style={{ border: "1px solid #e2e8f0", borderRadius: 8, padding: "0.75rem" }}> <div key={p.id} style={{ border: "1px solid #e5e7eb", borderRadius: 10, padding: "0.85rem", background: "#fff", boxShadow: "0 1px 3px rgba(0, 0, 0, 0.04)" }}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}> <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
<strong style={{ fontSize: 15 }}>{p.name}</strong> <strong style={{ fontSize: 15 }}>{p.name}</strong>
<div style={{ display: "flex", gap: "0.3rem" }}> <div style={{ display: "flex", gap: "0.3rem" }}>
@@ -498,7 +498,7 @@ export function ClientsPage() {
</Field> </Field>
{clientFormError && <p style={{ color: "red", margin: "0.5rem 0 0" }}>{clientFormError}</p>} {clientFormError && <p style={{ color: "red", margin: "0.5rem 0 0" }}>{clientFormError}</p>}
<div style={{ display: "flex", gap: "0.5rem", marginTop: "1rem" }}> <div style={{ display: "flex", gap: "0.5rem", marginTop: "1rem" }}>
<button type="submit" disabled={savingClient} style={{ ...btnStyle, backgroundColor: "#3b82f6", color: "#fff", borderColor: "#3b82f6" }}> <button type="submit" disabled={savingClient} style={{ ...btnStyle, backgroundColor: "#4f8a6f", color: "#fff", borderColor: "#4f8a6f" }}>
{savingClient ? "Saving…" : editingClient ? "Save Changes" : "Create Client"} {savingClient ? "Saving…" : editingClient ? "Save Changes" : "Create Client"}
</button> </button>
<button type="button" onClick={() => setShowClientForm(false)} style={btnStyle}>Cancel</button> <button type="button" onClick={() => setShowClientForm(false)} style={btnStyle}>Cancel</button>
@@ -637,7 +637,7 @@ export function ClientsPage() {
</Field> </Field>
{logFormError && <p style={{ color: "red", margin: "0.5rem 0 0" }}>{logFormError}</p>} {logFormError && <p style={{ color: "red", margin: "0.5rem 0 0" }}>{logFormError}</p>}
<div style={{ display: "flex", gap: "0.5rem", marginTop: "1rem" }}> <div style={{ display: "flex", gap: "0.5rem", marginTop: "1rem" }}>
<button type="submit" disabled={savingLog} style={{ ...btnStyle, backgroundColor: "#3b82f6", color: "#fff", borderColor: "#3b82f6" }}> <button type="submit" disabled={savingLog} style={{ ...btnStyle, backgroundColor: "#4f8a6f", color: "#fff", borderColor: "#4f8a6f" }}>
{savingLog ? "Saving…" : "Save Visit Log"} {savingLog ? "Saving…" : "Save Visit Log"}
</button> </button>
<button type="button" onClick={() => setShowLogForm(false)} style={btnStyle}>Cancel</button> <button type="button" onClick={() => setShowLogForm(false)} style={btnStyle}>Cancel</button>
@@ -674,9 +674,9 @@ function Field({ label, children }: { label: string; children: React.ReactNode }
} }
const btnStyle: React.CSSProperties = { const btnStyle: React.CSSProperties = {
padding: "0.35rem 0.75rem", border: "1px solid #d1d5db", borderRadius: 4, background: "#f9fafb", cursor: "pointer", fontSize: 13, padding: "0.4rem 0.85rem", border: "1px solid #d1d5db", borderRadius: 6, background: "#fff", cursor: "pointer", fontSize: 13, fontWeight: 500,
}; };
const inputStyle: React.CSSProperties = { const inputStyle: React.CSSProperties = {
width: "100%", padding: "0.4rem 0.5rem", border: "1px solid #d1d5db", borderRadius: 4, fontSize: 14, boxSizing: "border-box", width: "100%", padding: "0.45rem 0.6rem", border: "1px solid #d1d5db", borderRadius: 6, fontSize: 14, boxSizing: "border-box",
}; };
+10 -9
View File
@@ -287,7 +287,7 @@ function NewGroupBookingForm({
<button <button
type="submit" type="submit"
disabled={saving} disabled={saving}
style={{ ...btnStyle, backgroundColor: "#3b82f6", color: "#fff", borderColor: "#3b82f6" }} style={{ ...btnStyle, backgroundColor: "#4f8a6f", color: "#fff", borderColor: "#4f8a6f" }}
> >
{saving ? "Booking…" : "Create Group Booking"} {saving ? "Booking…" : "Create Group Booking"}
</button> </button>
@@ -471,7 +471,7 @@ export function GroupBookingPage() {
</select> </select>
<button <button
onClick={() => setShowCreate(true)} onClick={() => setShowCreate(true)}
style={{ ...btnStyle, marginLeft: "auto", backgroundColor: "#3b82f6", color: "#fff", borderColor: "#3b82f6" }} style={{ ...btnStyle, marginLeft: "auto", backgroundColor: "#4f8a6f", color: "#fff", borderColor: "#4f8a6f" }}
> >
+ New Group Booking + New Group Booking
</button> </button>
@@ -558,25 +558,26 @@ function Field({
} }
const btnStyle: React.CSSProperties = { const btnStyle: React.CSSProperties = {
padding: "0.35rem 0.75rem", padding: "0.4rem 0.85rem",
border: "1px solid #d1d5db", border: "1px solid #d1d5db",
borderRadius: 4, borderRadius: 6,
background: "#f9fafb", background: "#fff",
cursor: "pointer", cursor: "pointer",
fontSize: 13, fontSize: 13,
fontWeight: 500,
}; };
const inputStyle: React.CSSProperties = { const inputStyle: React.CSSProperties = {
width: "100%", width: "100%",
padding: "0.4rem 0.5rem", padding: "0.45rem 0.6rem",
border: "1px solid #d1d5db", border: "1px solid #d1d5db",
borderRadius: 4, borderRadius: 6,
fontSize: 13, fontSize: 13,
boxSizing: "border-box", boxSizing: "border-box",
}; };
const tdStyle: React.CSSProperties = { const tdStyle: React.CSSProperties = {
padding: "0.45rem 1rem", padding: "0.5rem 1rem",
borderBottom: "1px solid #f1f5f9", borderBottom: "1px solid #f3f4f6",
color: "#374151", color: "#374151",
}; };
+10 -8
View File
@@ -129,7 +129,7 @@ function CreateFromAppointmentForm({
<button <button
type="submit" type="submit"
disabled={saving || !selectedApptId} disabled={saving || !selectedApptId}
style={{ ...btnStyle, backgroundColor: "#3b82f6", color: "#fff", borderColor: "#3b82f6" }} style={{ ...btnStyle, backgroundColor: "#4f8a6f", color: "#fff", borderColor: "#4f8a6f" }}
> >
{saving ? "Creating…" : "Create Invoice"} {saving ? "Creating…" : "Create Invoice"}
</button> </button>
@@ -540,7 +540,7 @@ export function InvoicesPage() {
</select> </select>
<button <button
onClick={() => setShowCreate(true)} onClick={() => setShowCreate(true)}
style={{ ...btnStyle, backgroundColor: "#3b82f6", color: "#fff", borderColor: "#3b82f6", marginLeft: "auto" }} style={{ ...btnStyle, backgroundColor: "#4f8a6f", color: "#fff", borderColor: "#4f8a6f", marginLeft: "auto" }}
> >
+ Create Invoice + Create Invoice
</button> </button>
@@ -551,11 +551,12 @@ export function InvoicesPage() {
No invoices yet. Create one from a completed appointment. No invoices yet. Create one from a completed appointment.
</p> </p>
) : ( ) : (
<div style={{ background: "#fff", borderRadius: 8, border: "1px solid #e5e7eb", overflow: "hidden", boxShadow: "0 1px 3px rgba(0, 0, 0, 0.04)" }}>
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 14 }}> <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 14 }}>
<thead> <thead>
<tr style={{ background: "#f8fafc" }}> <tr style={{ background: "#f8fafc" }}>
{["Date", "Client", "Subtotal", "Tax", "Tip", "Total", "Status", ""].map((h) => ( {["Date", "Client", "Subtotal", "Tax", "Tip", "Total", "Status", ""].map((h) => (
<th key={h} style={{ textAlign: "left", padding: "0.5rem 0.75rem", borderBottom: "1px solid #e2e8f0" }}> <th key={h} style={{ textAlign: "left", padding: "0.55rem 0.75rem", borderBottom: "1px solid #e5e7eb", fontSize: 11, fontWeight: 600, color: "#6b7280", textTransform: "uppercase", letterSpacing: "0.04em" }}>
{h} {h}
</th> </th>
))} ))}
@@ -582,6 +583,7 @@ export function InvoicesPage() {
))} ))}
</tbody> </tbody>
</table> </table>
</div>
)} )}
{showCreate && ( {showCreate && (
@@ -647,15 +649,15 @@ function Field({ label, children }: { label: string; children: React.ReactNode }
} }
const btnStyle: React.CSSProperties = { const btnStyle: React.CSSProperties = {
padding: "0.35rem 0.75rem", border: "1px solid #d1d5db", padding: "0.4rem 0.85rem", border: "1px solid #d1d5db",
borderRadius: 4, background: "#f9fafb", cursor: "pointer", fontSize: 13, borderRadius: 6, background: "#fff", cursor: "pointer", fontSize: 13, fontWeight: 500,
}; };
const inputStyle: React.CSSProperties = { const inputStyle: React.CSSProperties = {
width: "100%", padding: "0.4rem 0.5rem", border: "1px solid #d1d5db", width: "100%", padding: "0.45rem 0.6rem", border: "1px solid #d1d5db",
borderRadius: 4, fontSize: 14, boxSizing: "border-box", borderRadius: 6, fontSize: 14, boxSizing: "border-box",
}; };
const tdStyle: React.CSSProperties = { const tdStyle: React.CSSProperties = {
padding: "0.5rem 0.75rem", borderBottom: "1px solid #e2e8f0", padding: "0.55rem 0.75rem", borderBottom: "1px solid #f3f4f6",
}; };
+40 -37
View File
@@ -84,14 +84,15 @@ function StatCard({ label, value, sub }: { label: string; value: string; sub?: s
<div <div
style={{ style={{
background: "#fff", background: "#fff",
border: "1px solid #e2e8f0", border: "1px solid #e5e7eb",
borderRadius: 8, borderRadius: 10,
padding: "1rem 1.25rem", padding: "1rem 1.25rem",
flex: 1, flex: 1,
minWidth: 140, minWidth: 140,
boxShadow: "0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04)",
}} }}
> >
<div style={{ fontSize: 12, color: "#6b7280", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em" }}> <div style={{ fontSize: 11, color: "#6b7280", fontWeight: 600, textTransform: "uppercase", letterSpacing: "0.05em" }}>
{label} {label}
</div> </div>
<div style={{ fontSize: 26, fontWeight: 700, margin: "0.25rem 0", color: "#111827" }}>{value}</div> <div style={{ fontSize: 26, fontWeight: 700, margin: "0.25rem 0", color: "#111827" }}>{value}</div>
@@ -102,7 +103,7 @@ function StatCard({ label, value, sub }: { label: string; value: string; sub?: s
function SectionHeader({ children }: { children: React.ReactNode }) { function SectionHeader({ children }: { children: React.ReactNode }) {
return ( return (
<h2 style={{ fontSize: 16, fontWeight: 700, margin: "1.5rem 0 0.75rem", color: "#111827", borderBottom: "1px solid #e2e8f0", paddingBottom: "0.4rem" }}> <h2 style={{ fontSize: 15, fontWeight: 700, margin: "1.75rem 0 0.75rem", color: "#1a202c", borderBottom: "2px solid #e5e7eb", paddingBottom: "0.5rem" }}>
{children} {children}
</h2> </h2>
); );
@@ -110,35 +111,37 @@ function SectionHeader({ children }: { children: React.ReactNode }) {
function Table({ headers, rows }: { headers: string[]; rows: (string | number)[][] }) { function Table({ headers, rows }: { headers: string[]; rows: (string | number)[][] }) {
return ( return (
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}> <div style={{ background: "#fff", borderRadius: 8, border: "1px solid #e5e7eb", overflow: "hidden", boxShadow: "0 1px 3px rgba(0, 0, 0, 0.04)" }}>
<thead> <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
<tr style={{ background: "#f8fafc" }}> <thead>
{headers.map((h) => ( <tr style={{ background: "#f8fafc" }}>
<th key={h} style={{ textAlign: "left", padding: "0.4rem 0.75rem", borderBottom: "1px solid #e2e8f0", fontWeight: 600, color: "#374151" }}> {headers.map((h) => (
{h} <th key={h} style={{ textAlign: "left", padding: "0.5rem 0.75rem", borderBottom: "1px solid #e5e7eb", fontWeight: 600, fontSize: 11, color: "#6b7280", textTransform: "uppercase", letterSpacing: "0.04em" }}>
</th> {h}
))} </th>
</tr>
</thead>
<tbody>
{rows.map((row, i) => (
<tr key={i} style={{ borderBottom: "1px solid #f1f5f9" }}>
{row.map((cell, j) => (
<td key={j} style={{ padding: "0.4rem 0.75rem", color: "#374151" }}>
{cell}
</td>
))} ))}
</tr> </tr>
))} </thead>
{rows.length === 0 && ( <tbody>
<tr> {rows.map((row, i) => (
<td colSpan={headers.length} style={{ padding: "1rem 0.75rem", color: "#9ca3af" }}> <tr key={i} style={{ borderBottom: "1px solid #f3f4f6" }}>
No data for this period. {row.map((cell, j) => (
</td> <td key={j} style={{ padding: "0.5rem 0.75rem", color: "#374151" }}>
</tr> {cell}
)} </td>
</tbody> ))}
</table> </tr>
))}
{rows.length === 0 && (
<tr>
<td colSpan={headers.length} style={{ padding: "1.5rem 0.75rem", color: "#9ca3af", textAlign: "center" }}>
No data for this period.
</td>
</tr>
)}
</tbody>
</table>
</div>
); );
} }
@@ -267,7 +270,7 @@ export function ReportsPage() {
<option value="month">Month</option> <option value="month">Month</option>
</select> </select>
</label> </label>
<button onClick={loadAll} style={{ ...btnStyle, background: "#1d4ed8", color: "#fff", borderColor: "#1d4ed8" }}> <button onClick={loadAll} style={{ ...btnStyle, background: "#4f8a6f", color: "#fff", borderColor: "#4f8a6f" }}>
{loading ? "Loading…" : "Refresh"} {loading ? "Loading…" : "Refresh"}
</button> </button>
<div style={{ marginLeft: "auto", display: "flex", gap: "0.5rem" }}> <div style={{ marginLeft: "auto", display: "flex", gap: "0.5rem" }}>
@@ -390,19 +393,19 @@ export function ReportsPage() {
// ─── Shared styles ──────────────────────────────────────────────────────────── // ─── Shared styles ────────────────────────────────────────────────────────────
const btnStyle: React.CSSProperties = { const btnStyle: React.CSSProperties = {
padding: "0.35rem 0.75rem", padding: "0.4rem 0.85rem",
border: "1px solid #d1d5db", border: "1px solid #d1d5db",
borderRadius: 4, borderRadius: 6,
background: "#f9fafb", background: "#fff",
cursor: "pointer", cursor: "pointer",
fontSize: 13, fontSize: 13,
fontWeight: 500, fontWeight: 500,
}; };
const inputStyle: React.CSSProperties = { const inputStyle: React.CSSProperties = {
padding: "0.3rem 0.4rem", padding: "0.35rem 0.5rem",
border: "1px solid #d1d5db", border: "1px solid #d1d5db",
borderRadius: 4, borderRadius: 6,
fontSize: 13, fontSize: 13,
marginLeft: "0.25rem", marginLeft: "0.25rem",
}; };
+10 -8
View File
@@ -119,7 +119,7 @@ export function ServicesPage() {
<h1 style={{ margin: 0 }}>Services</h1> <h1 style={{ margin: 0 }}>Services</h1>
<button <button
onClick={openNew} onClick={openNew}
style={{ ...btnStyle, backgroundColor: "#3b82f6", color: "#fff", borderColor: "#3b82f6", marginLeft: "auto" }} style={{ ...btnStyle, backgroundColor: "#4f8a6f", color: "#fff", borderColor: "#4f8a6f", marginLeft: "auto" }}
> >
+ Add Service + Add Service
</button> </button>
@@ -128,11 +128,12 @@ export function ServicesPage() {
{services.length === 0 ? ( {services.length === 0 ? (
<p>No services configured yet.</p> <p>No services configured yet.</p>
) : ( ) : (
<div style={{ background: "#fff", borderRadius: 8, border: "1px solid #e5e7eb", overflow: "hidden", boxShadow: "0 1px 3px rgba(0, 0, 0, 0.04)" }}>
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 14 }}> <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 14 }}>
<thead> <thead>
<tr style={{ background: "#f8fafc" }}> <tr style={{ background: "#f8fafc" }}>
{["Name", "Description", "Price", "Duration", "Status", ""].map((h) => ( {["Name", "Description", "Price", "Duration", "Status", ""].map((h) => (
<th key={h} style={{ textAlign: "left", padding: "0.5rem 0.75rem", borderBottom: "1px solid #e2e8f0" }}> <th key={h} style={{ textAlign: "left", padding: "0.55rem 0.75rem", borderBottom: "1px solid #e5e7eb", fontSize: 11, fontWeight: 600, color: "#6b7280", textTransform: "uppercase", letterSpacing: "0.04em" }}>
{h} {h}
</th> </th>
))} ))}
@@ -171,6 +172,7 @@ export function ServicesPage() {
))} ))}
</tbody> </tbody>
</table> </table>
</div>
)} )}
{showForm && ( {showForm && (
@@ -230,7 +232,7 @@ export function ServicesPage() {
<button <button
type="submit" type="submit"
disabled={saving} disabled={saving}
style={{ ...btnStyle, backgroundColor: "#3b82f6", color: "#fff", borderColor: "#3b82f6" }} style={{ ...btnStyle, backgroundColor: "#4f8a6f", color: "#fff", borderColor: "#4f8a6f" }}
> >
{saving ? "Saving…" : editing ? "Save Changes" : "Create Service"} {saving ? "Saving…" : editing ? "Save Changes" : "Create Service"}
</button> </button>
@@ -277,15 +279,15 @@ function Field({ label, children }: { label: string; children: React.ReactNode }
} }
const btnStyle: React.CSSProperties = { const btnStyle: React.CSSProperties = {
padding: "0.35rem 0.75rem", border: "1px solid #d1d5db", padding: "0.4rem 0.85rem", border: "1px solid #d1d5db",
borderRadius: 4, background: "#f9fafb", cursor: "pointer", fontSize: 13, borderRadius: 6, background: "#fff", cursor: "pointer", fontSize: 13, fontWeight: 500,
}; };
const inputStyle: React.CSSProperties = { const inputStyle: React.CSSProperties = {
width: "100%", padding: "0.4rem 0.5rem", border: "1px solid #d1d5db", width: "100%", padding: "0.45rem 0.6rem", border: "1px solid #d1d5db",
borderRadius: 4, fontSize: 14, boxSizing: "border-box", borderRadius: 6, fontSize: 14, boxSizing: "border-box",
}; };
const tdStyle: React.CSSProperties = { const tdStyle: React.CSSProperties = {
padding: "0.5rem 0.75rem", borderBottom: "1px solid #e2e8f0", padding: "0.55rem 0.75rem", borderBottom: "1px solid #f3f4f6",
}; };
+8 -6
View File
@@ -78,7 +78,7 @@ export function StaffPage() {
<div style={{ fontFamily: "system-ui, sans-serif" }}> <div style={{ fontFamily: "system-ui, sans-serif" }}>
<div style={{ display: "flex", alignItems: "center", gap: "1rem", marginBottom: "1rem" }}> <div style={{ display: "flex", alignItems: "center", gap: "1rem", marginBottom: "1rem" }}>
<h1 style={{ margin: 0 }}>Staff</h1> <h1 style={{ margin: 0 }}>Staff</h1>
<button onClick={openNew} style={{ ...btnStyle, backgroundColor: "#3b82f6", color: "#fff", borderColor: "#3b82f6", marginLeft: "auto" }}> <button onClick={openNew} style={{ ...btnStyle, backgroundColor: "#4f8a6f", color: "#fff", borderColor: "#4f8a6f", marginLeft: "auto" }}>
+ Add Staff + Add Staff
</button> </button>
</div> </div>
@@ -86,11 +86,12 @@ export function StaffPage() {
{staff.length === 0 ? ( {staff.length === 0 ? (
<p>No staff members yet.</p> <p>No staff members yet.</p>
) : ( ) : (
<div style={{ background: "#fff", borderRadius: 8, border: "1px solid #e5e7eb", overflow: "hidden", boxShadow: "0 1px 3px rgba(0, 0, 0, 0.04)" }}>
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 14 }}> <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 14 }}>
<thead> <thead>
<tr style={{ background: "#f8fafc" }}> <tr style={{ background: "#f8fafc" }}>
{["Name", "Email", "Role", "Status", ""].map((h) => ( {["Name", "Email", "Role", "Status", ""].map((h) => (
<th key={h} style={{ textAlign: "left", padding: "0.5rem 0.75rem", borderBottom: "1px solid #e2e8f0" }}>{h}</th> <th key={h} style={{ textAlign: "left", padding: "0.55rem 0.75rem", borderBottom: "1px solid #e5e7eb", fontSize: 11, fontWeight: 600, color: "#6b7280", textTransform: "uppercase", letterSpacing: "0.04em" }}>{h}</th>
))} ))}
</tr> </tr>
</thead> </thead>
@@ -113,6 +114,7 @@ export function StaffPage() {
))} ))}
</tbody> </tbody>
</table> </table>
</div>
)} )}
{showForm && ( {showForm && (
@@ -143,7 +145,7 @@ export function StaffPage() {
</div> </div>
{formError && <p style={{ color: "red", margin: "0.5rem 0 0" }}>{formError}</p>} {formError && <p style={{ color: "red", margin: "0.5rem 0 0" }}>{formError}</p>}
<div style={{ display: "flex", gap: "0.5rem", marginTop: "1rem" }}> <div style={{ display: "flex", gap: "0.5rem", marginTop: "1rem" }}>
<button type="submit" disabled={saving} style={{ ...btnStyle, backgroundColor: "#3b82f6", color: "#fff", borderColor: "#3b82f6" }}> <button type="submit" disabled={saving} style={{ ...btnStyle, backgroundColor: "#4f8a6f", color: "#fff", borderColor: "#4f8a6f" }}>
{saving ? "Saving…" : editing ? "Save Changes" : "Add Staff"} {saving ? "Saving…" : editing ? "Save Changes" : "Add Staff"}
</button> </button>
<button type="button" onClick={() => setShowForm(false)} style={btnStyle}>Cancel</button> <button type="button" onClick={() => setShowForm(false)} style={btnStyle}>Cancel</button>
@@ -156,7 +158,7 @@ export function StaffPage() {
); );
} }
const btnStyle: React.CSSProperties = { padding: "0.35rem 0.75rem", border: "1px solid #d1d5db", borderRadius: 4, background: "#f9fafb", cursor: "pointer", fontSize: 13 }; const btnStyle: React.CSSProperties = { padding: "0.4rem 0.85rem", border: "1px solid #d1d5db", borderRadius: 6, background: "#fff", cursor: "pointer", fontSize: 13, fontWeight: 500 };
const inputStyle: React.CSSProperties = { width: "100%", padding: "0.4rem 0.5rem", border: "1px solid #d1d5db", borderRadius: 4, fontSize: 14, boxSizing: "border-box" }; const inputStyle: React.CSSProperties = { width: "100%", padding: "0.45rem 0.6rem", border: "1px solid #d1d5db", borderRadius: 6, fontSize: 14, boxSizing: "border-box" };
const labelStyle: React.CSSProperties = { display: "block", fontWeight: 600, marginBottom: "0.25rem", fontSize: 13, color: "#374151" }; const labelStyle: React.CSSProperties = { display: "block", fontWeight: 600, marginBottom: "0.25rem", fontSize: 13, color: "#374151" };
const tdStyle: React.CSSProperties = { padding: "0.5rem 0.75rem", borderBottom: "1px solid #e2e8f0" }; const tdStyle: React.CSSProperties = { padding: "0.55rem 0.75rem", borderBottom: "1px solid #f3f4f6" };