diff --git a/apps/hq-web/src/Layout.tsx b/apps/hq-web/src/Layout.tsx
index ed01b30..79aa3c2 100644
--- a/apps/hq-web/src/Layout.tsx
+++ b/apps/hq-web/src/Layout.tsx
@@ -1,9 +1,9 @@
import { useEffect, useState } from 'react'
import { Navigate, NavLink, Outlet, useNavigate } from 'react-router-dom'
import { Badge, ThemeSwitcher } from '@sims/ui'
-import { clearSession, displayName, getEmailStatus, hasSession } from './api'
+import { clearSession, displayName, getEmailStatus, hasSession, role } from './api'
-const NAV = [
+const BASE_NAV = [
{ to: '/', label: 'Dashboard' },
{ to: '/clients', label: 'Clients' },
{ to: '/modules', label: 'Modules' },
@@ -25,6 +25,9 @@ export function Layout() {
if (!hasSession()) return
+ // Company (the company.* settings editor) is owner-only — staff never see the item.
+ const nav_items = role() === 'owner' ? [...BASE_NAV, { to: '/settings/company', label: 'Company' }] : BASE_NAV
+
return (
- {NAV.map((n) => (
+ {nav_items.map((n) => (
(isActive ? 'active' : '')} end={n.to === '/'}>
{n.label}
diff --git a/apps/hq-web/src/main.tsx b/apps/hq-web/src/main.tsx
index 17fbf78..ac196ee 100644
--- a/apps/hq-web/src/main.tsx
+++ b/apps/hq-web/src/main.tsx
@@ -12,6 +12,7 @@ import { Clients } from './pages/Clients'
import { ClientDetail } from './pages/ClientDetail'
import { Modules } from './pages/Modules'
import { Reports } from './pages/Reports'
+import { CompanyProfile } from './pages/CompanyProfile'
import { NewDocument } from './pages/NewDocument'
import { DocumentView } from './pages/DocumentView'
@@ -26,6 +27,7 @@ function App() {
} />
} />
} />
+ } />
} />
} />
} />
diff --git a/apps/hq-web/src/pages/ClientDetail.tsx b/apps/hq-web/src/pages/ClientDetail.tsx
index fbd133c..6af08f6 100644
--- a/apps/hq-web/src/pages/ClientDetail.tsx
+++ b/apps/hq-web/src/pages/ClientDetail.tsx
@@ -8,6 +8,7 @@ import {
role, getRecurringPlans, createRecurringPlan, deactivateRecurringPlan,
getAmc, createAmc, deactivateAmc, generateAmcRenewalInvoice,
getInteractions, getInteractionTypes, createInteraction, updateInteraction,
+ getClientAwsUsage,
CLIENT_MODULE_STATUSES, CLIENT_STATUSES, KIND_LABEL, PAYMENT_MODES,
type AmcPaidStatus, type ClientModule, type ClientModuleStatus, type ClientStatus,
type InteractionType, type Kind, type Module, type Outcome, type PaymentMode,
@@ -32,6 +33,7 @@ export function ClientDetail() {
const amc = useData(() => getAmc(id), [id])
const interactions = useData(() => getInteractions(id), [id])
const types = useData(getInteractionTypes, [])
+ const awsUsage = useData(() => getClientAwsUsage(id), [id])
const isOwner = role() === 'owner'
const [actionErr, setActionErr] = useState()
@@ -278,6 +280,23 @@ export function ClientDetail() {
}))}
/>
)}
+
+ AWS usage
+ {awsUsage.error !== undefined && {awsUsage.error}}
+ {awsUsage.data === undefined || awsUsage.data.length === 0
+ ? No AWS usage recorded for this client.
+ : (
+ ({
+ month: u.month, storage: u.storageGb, transfer: u.transferGb, cost: inr(u.costPaise), source: u.source,
+ }))}
+ />
+ )}
)
}
diff --git a/apps/hq-web/src/pages/CompanyProfile.tsx b/apps/hq-web/src/pages/CompanyProfile.tsx
new file mode 100644
index 0000000..fc6f333
--- /dev/null
+++ b/apps/hq-web/src/pages/CompanyProfile.tsx
@@ -0,0 +1,54 @@
+import { useEffect, useState } from 'react'
+import { Navigate } from 'react-router-dom'
+import { Button, Field, Notice, PageHeader } from '@sims/ui'
+import { getCompanyProfile, putCompanyProfile, role } from '../api'
+
+const FIELDS: { key: string; setting: string; label: string }[] = [
+ { key: 'name', setting: 'company.name', label: 'Company name' },
+ { key: 'address', setting: 'company.address', label: 'Address' },
+ { key: 'gstin', setting: 'company.gstin', label: 'GSTIN' },
+ { key: 'stateCode', setting: 'company.state_code', label: 'State code' },
+ { key: 'phone', setting: 'company.phone', label: 'Phone' },
+ { key: 'email', setting: 'company.email', label: 'Email' },
+ { key: 'bank', setting: 'company.bank', label: 'Bank details' },
+]
+
+/** Owner-only editor for the company.* settings the letterhead PDFs read. */
+export function CompanyProfile() {
+ const [f, setF] = useState>({})
+ const [msg, setMsg] = useState<{ tone: 'ok' | 'err'; text: string } | undefined>()
+ const [saving, setSaving] = useState(false)
+
+ useEffect(() => {
+ getCompanyProfile()
+ .then((c) => setF(Object.fromEntries(FIELDS.map((x) => [x.key, c[x.setting] ?? '']))))
+ .catch((e: Error) => setMsg({ tone: 'err', text: e.message }))
+ }, [])
+
+ if (role() !== 'owner') return
+
+ const save = () => {
+ setSaving(true); setMsg(undefined)
+ putCompanyProfile(f)
+ .then((c) => { setF(Object.fromEntries(FIELDS.map((x) => [x.key, c[x.setting] ?? '']))); setMsg({ tone: 'ok', text: 'Saved. PDFs use the new details immediately.' }) })
+ .catch((e: Error) => setMsg({ tone: 'err', text: e.message }))
+ .finally(() => setSaving(false))
+ }
+
+ return (
+
+
+ {msg !== undefined &&
{msg.text}}
+
+ {FIELDS.map((x) => (
+
+ {x.key === 'address' || x.key === 'bank'
+ ?
+ ))}
+
+
+
+ )
+}