Branch Management
Complete reference for the branch administration page — covering the stats dashboard, dual-view listing (desktop table + mobile cards), cascading state/city location system, full CRUD modals, and the branch_api.php payload contracts.
What This Page Does
The Branch Management page (manage-branches.php) provides a full CRUD interface for administering company branch locations. Administrators can create, view, edit, and delete branches — each with a name, unique code, address, state/city location, and optional bank account details.
The page loads all branches from branch_api.php on init, displays them in a desktop data table or mobile card layout, and exposes three modal dialogs for Add/Edit, View, and Delete operations. A live search input debounces queries at 350 ms and re-fetches filtered results from the API.
Page Structure
The page is built from five logical areas inside <main>, plus three modal overlays that sit outside the layout flow.
Stats are derived client-side from the loaded branches array — no separate API call. Each card has a colour-coded 3px top stripe (blue, sage, rust, slate).
Columns: ID · Branch (icon + name + address) · Code · Location (city + state) · Bank Details · Created · Actions (View / Edit / Delete).
Each card shows branch icon, name, code chip, and a 2-column meta grid (city, state, bank, account number). A coloured left-border stripe rotates through blue → sage → rust on every third card.
All three modals share the same CSS overlay + enter animation. Clicking outside any modal or pressing its close button calls closeModal(id).
API Endpoints
Three PHP files are consumed. All return the standard {"success": bool, "data": ...} JSON envelope.
branch_comp_fk column is intentionally excluded from all client-side payloads. The backend sets it automatically from the session's $cp_id. This prevents cross-company data injection.Stats Dashboard
Four KPI cards are rendered above the table. All four metrics are computed client-side from the branches array immediately after data loads — no additional API requests are made.
const tot = branches.length; const states = new Set(branches.map(b => b.branch_state).filter(Boolean)).size; const banked = branches.filter(b => b.branch_bank).length; const cities = new Set(branches.map(b => b.branch_city).filter(Boolean)).size;
Branch Listing
The listing renders identically from the same branches array into two parallel outputs — a desktop HTML table and a mobile card list. Both are always kept in sync. A search field at the top right triggers debSearch() with a 350 ms debounce, which re-calls load() with the ?search= query parameter.
A Branch Icons
Each branch is represented by a coloured circular/rounded icon with 1–2 letter initials, generated deterministically from the branch name using a simple hash function. The same icon is used consistently in the table row, mobile card, and view modal.
const iconColors = ['#2563eb','#1b3560','#5a7a62','#b85c38','#4a5568','#0a1628']; // Consistent colour from name hash: function bColor(s) { let h = 0; for (let i = 0; i < s.length; i++) h = s.charCodeAt(i) + ((h << 5) - h); return iconColors[Math.abs(h) % iconColors.length]; } // 1–2 letter initials from name words: function bInit(n) { return (n || 'BR').split(' ').slice(0,2) .map(w => w[0]?.toUpperCase() || '').join(''); }
B Skeleton Loading
While the initial data fetch is in progress, the table body is pre-populated with three skeleton rows using the .sk shimmer animation class. This prevents layout shift and gives visual feedback that loading is in progress.
C Empty & Error States
If the API returns an empty array, both the table and mobile card container render a centred empty state with a heading and helper text. If the fetch itself fails, an error banner appears at the top of the page and the table shows a connection error message.
Location Cascade
The branch form uses a two-level cascading location system. State and City are both stored in the database as plain text strings (VARCHAR columns), not foreign key IDs. This means the <option value> for both selects is the name, not the numeric ID.
branch_state and branch_city store the text name (e.g. "Lagos", "Ikeja"). The state_id from the location API is kept only in data-id on each <option> and is used solely to look up cities. It is never written to the database.async function onStateChange(sel) { const stateName = sel.value; // stored as-is in DB const stateId = sel.options[sel.selectedIndex]?.dataset?.id; // used for city lookup only // Fetch cities if not already cached if (!citiesCache[stateId]) { const r = await fetch(`${LOC_API}?type=cities&state_id=${stateId}`); const j = await r.json(); citiesCache[stateId] = j.data; // ← in-memory cache } populateCitySelect(stateId); // options use city_name as value }
Cities are cached in citiesCache (keyed by state_id) so switching between states does not re-fetch previously loaded cities. When editing an existing branch, preselectLocation(stateName, cityName) looks up the matching state object, fetches/loads cities if needed, and pre-selects both dropdowns by name.
Add / Edit Modal
The same #formModal is reused for both create and update operations. The modal title and editId variable distinguish the mode. Saving calls saveBranch() which POSTs or PUTs to branch_api.php based on whether editId is set.
A Form Fields
| Field ID | Type | Required | Description |
|---|---|---|---|
| fName | text | Required | Branch full name (e.g. "Lagos Island Branch"). Maps to branch_name. |
| fCode | text | Required | Unique branch code, auto-uppercased via style="text-transform:uppercase" and .toUpperCase() in JS before sending. Maps to branch_code. |
| fAddr | textarea | Required | Full street address. 2-row textarea, resizable. Maps to branch_address. |
| fState | select | Required | State name string (option value = name). Triggers city cascade on change. Maps to branch_state. |
| fCity | select | Required | Disabled until a state is selected. City name string (option value = name). Maps to branch_city. |
| fBank | text | Optional | Bank name (e.g. "First Bank of Nigeria"). Maps to branch_bank. |
| fAcctNo | text | Optional | Account number, max 20 chars. Maps to branch_acct_no. |
| fAcctName | text | Optional | Account name / holder. Maps to branch_acct_name. |
$cp_id). clearForm() also omits this field entirely when resetting inputs.View Modal
Opens when the eye icon is clicked in the actions column. Renders a detail card entirely from the in-memory branches array — no additional API call. An "Edit Branch" button in the modal footer immediately opens the edit form pre-filled with the same record.
Delete Modal
A compact confirmation dialog (max-width 360px). The branch name is shown in the confirmation message to prevent accidental deletion. Confirming calls confirmDelete() which sends a DELETE request to branch_api.php?id={delId}.
| Element | Behaviour |
|---|---|
| Cancel button | Calls closeModal('delModal'). No changes made. |
| Delete button | Calls confirmDelete(). Button is disabled + spinner shown during request. On success: toast shown, modal closed, load() re-fetches the list. |
| #delName | Set to b.branch_name from the in-memory array before the modal opens. Displayed in the confirmation message body. |
DELETE request permanently removes the branch record. The backend should enforce any referential integrity checks (e.g. preventing deletion of branches that have active loans).Database Schema
The branches table columns inferred from the page's field mappings and rendered data.
| Column | Type | Notes |
|---|---|---|
| branch_id | int PK | Auto-increment primary key. Shown as #ID in the table. |
| branch_comp_fk | int FK | Company foreign key. Set server-side from session $cp_id. Never sent from the client. |
| branch_name | varchar | Human-readable branch name. Used to derive icon initials and colour. Required. |
| branch_code | varchar | Unique short identifier (e.g. LG-001). Stored uppercase. Rendered as a blue chip badge. |
| branch_address | varchar / text | Full street address. Truncated with ellipsis in the table's name cell sub-line. |
| branch_state | varchar | State name string — not a foreign key ID. Populated from location API option values. |
| branch_city | varchar | City name string — not a foreign key ID. Populated from location API city option values. |
| branch_bank | varchar | Optional bank name. Presence determines the "With Bank Info" stat count. |
| branch_acct_no | varchar(20) | Optional account number. Rendered in monospace font. |
| branch_acct_name | varchar | Optional account holder name. |
| branch_created_at | datetime | Creation timestamp. Formatted via fDate() helper (e.g. "12 Jan 2025"). |
API Payload
Both POST (create) and PUT (update) send identical JSON bodies. The only difference is the endpoint URL — PUT appends ?id={editId}.
{
"branch_name": "Lagos Island Branch",
"branch_code": "LG-001", // always uppercase
"branch_address": "15 Marina Street…",
"branch_state": "Lagos", // name string, not ID
"branch_city": "Lagos Island", // name string, not ID
"branch_bank": "First Bank of Nigeria",
"branch_acct_no": "0123456789",
"branch_acct_name":"Sodlapp Microfinance Ltd"
// branch_comp_fk intentionally omitted — set server-side
}
A Success Response
{
"success": true,
"data": { "branch_id": 14 } // on create; null on update/delete
}
B Payload Fields
| Key | Type | Notes |
|---|---|---|
| branch_name | string | Trimmed. Required — validated before POST. |
| branch_code | string | Trimmed and uppercased via .toUpperCase(). Required. |
| branch_address | string | Trimmed. Required. |
| branch_state | string | The state name text from the dropdown value. Not the numeric ID. Required. |
| branch_city | string | The city name text from the dropdown value. Not the numeric ID. Required. |
| branch_bank | string | Optional. Empty string if not filled. |
| branch_acct_no | string | Optional. Max 20 chars enforced by HTML maxlength attribute. |
| branch_acct_name | string | Optional. Empty string if not filled. |
JavaScript State Variables
| Variable | Type | Description |
|---|---|---|
| branches | array | The full list of branch objects from the last API fetch. All render functions (table, cards, modals) read from this in-memory array. |
| search | string | Current search query string. Passed as ?search= param to load(). Reset to empty on page load. |
| editId | number|null | null when adding a new branch. Set to branch_id when editing. Determines whether saveBranch() uses POST or PUT. |
| delId | number|null | ID of the branch queued for deletion. Set by openDel(id), read by confirmDelete(). |
| sTimer | timeout | Debounce timer for the search input. Cleared and reset on every keystroke via debSearch() (350 ms delay). |
| allStates | array | States loaded from location_api.php?type=states on init. Cached for the lifetime of the page; no re-fetch. |
| citiesCache | object | In-memory city cache keyed by state_id: { "5": [...cities] }. Prevents redundant city API calls when switching states. |
Functions
| Function | Trigger | Description |
|---|---|---|
| init() | Page load | Calls loadStates() then load() in sequence. States must be loaded before any form opens. |
| load() | init + search + CRUD success | Fetches branch_api.php with optional ?search= param. Stores result in branches, then calls render(). Shows error banner on failure. |
| render() | After load() | Computes stats, builds desktop table HTML, builds mobile card HTML. Handles empty state. Both views always updated together. |
| saveBranch() | Save button | Validates required fields, builds payload (no comp FK), POSTs or PUTs depending on editId. Shows loading spinner. On success: toast, close modal, reload. |
| confirmDelete() | Delete confirm btn | Sends DELETE to branch_api.php?id={delId}. Disables button during request. On success: toast, close modal, reload. |
| openAdd() | Add Branch button | Sets editId = null, calls clearForm(), opens #formModal. |
| openEdit(id) | Edit icon / View modal | Sets editId, populates all form fields from branches array, calls preselectLocation(), opens #formModal. |
| openView(id) | View icon | Builds HTML for the view card from branches array, wires "Edit Branch" button, opens #viewModal. |
| openDel(id) | Delete icon | Sets delId and #delName, opens #delModal. |
| loadStates() | init() | Fetches location_api.php?type=states, stores in allStates, then calls populateStateSelect('fState'). |
| onStateChange(sel) | fState select | Reads data-id attribute from selected option, fetches/caches city list, calls populateCitySelect(). |
| preselectLocation(state, city) | openEdit() | Repopulates state select with existing value pre-selected, fetches cities if uncached, then pre-selects city by name. |
| debSearch(v) | Search input | Sets search = v.trim(), debounces 350 ms, then calls load(). |
| clearForm() | openAdd() | Clears all text inputs and textarea. Resets state select via populateStateSelect(). Resets city select to disabled placeholder. Does not clear a comp FK field (none exists in form). |
| bColor(s) | render() | Deterministic colour from branch name hash. Returns one of 6 brand colours. |
| bInit(n) | render() | 1–2 uppercase initial letters from branch name words. |
| fDate(d) | render() | Formats a date string to "12 Jan 2025" using toLocaleDateString('en-GB'). Returns '—' for null/undefined. |
Validation Rules
Client-side validation runs inside saveBranch() before the API call. Failing fields are highlighted red for 1.5 s via hl(id) and the field is given focus. There is no error banner for form validation — the field highlight alone indicates what needs fixing.
| Order | Check | Field highlighted | Behaviour |
|---|---|---|---|
| #1 | Name required | fName | Calls hl('fName') and returns early if empty after trim. |
| #2 | Code required | fCode | Calls hl('fCode') and returns early if empty after trim. |
| #3 | Address required | fAddr | Calls hl('fAddr') and returns early if empty after trim. |
| #4 | State required | fState | Calls hl('fState') and returns early if no state selected. |
| #5 | City required | fCity | Calls hl('fCity') and returns early if no city selected. |
fBank, fAcctNo, and fAcctName are not validated client-side. They are sent as empty strings if left blank. The backend should store NULL or empty string as appropriate.branch_api.php should independently validate all required fields and enforce unique constraints (e.g. duplicate branch codes within the same company). Any success: false response surfaces via the toast notification.