/ Docs / Branch Management
Page Docs manage-branches
Overview Page Structure API Endpoints Stats Dashboard Branch Listing Location Cascade Add / Edit Modal View Modal Delete Modal DB Schema API Payload JS State Variables Functions Validation Rules
Page Documentation manage-branches.php

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.

CRUD Operations Live Search State → City Cascade Dual-View Listing Bank Details

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.

1 Page loads
2 States & Branches fetched
3 Stats + Table rendered
4 User CRUD via modals
5 Reload on success

Page Structure

The page is built from five logical areas inside <main>, plus three modal overlays that sit outside the layout flow.

Stats Row
.stats-row · Four KPI cards: Total Branches, States Covered, With Bank Info, Cities Covered

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).

Desktop Table
.table-wrapper · Hidden on mobile (≤768 px), 7-column data table with skeleton loading

Columns: ID · Branch (icon + name + address) · Code · Location (city + state) · Bank Details · Created · Actions (View / Edit / Delete).

Mobile Cards
.m-cards · Shown only on mobile, flex column of branch cards

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.

Three Modal Dialogs
Add/Edit · View · Delete — each uses the shared .m-overlay pattern

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.

GETapi/branch_apiList all branches (with optional ?search=)
POSTapi/branch_apiCreate new branch (JSON body)
PUTapi/branch_api?id={branch_id}Update existing branch (JSON body)
DELETEapi/branch_api?id={branch_id}Delete branch by ID
GETapi/location_api?type=statesLoad all states for the form dropdown
GETapi/location_api?type=cities&state_id={id}Load cities for a selected state (cached)
company_fk is server-side only The 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.

Total Branches
12
All locations
States Covered
7
Unique states
With Bank Info
9
Account linked
Cities Covered
11
Unique cities
render() — stats computation JavaScript
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.

Icon colour + initials generation JavaScript
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.

Name strings, not IDs 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.
onStateChange() — cascade flow JavaScript
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 IDTypeRequiredDescription
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.
No branch_comp_fk field The company foreign key is deliberately absent from the form. The server assigns it from the authenticated session ($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}.

ElementBehaviour
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.
Permanent deletion There is no soft-delete or recycle bin. A successful 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.

ColumnTypeNotes
branch_idint PKAuto-increment primary key. Shown as #ID in the table.
branch_comp_fkint FKCompany foreign key. Set server-side from session $cp_id. Never sent from the client.
branch_namevarcharHuman-readable branch name. Used to derive icon initials and colour. Required.
branch_codevarcharUnique short identifier (e.g. LG-001). Stored uppercase. Rendered as a blue chip badge.
branch_addressvarchar / textFull street address. Truncated with ellipsis in the table's name cell sub-line.
branch_statevarcharState name string — not a foreign key ID. Populated from location API option values.
branch_cityvarcharCity name string — not a foreign key ID. Populated from location API city option values.
branch_bankvarcharOptional bank name. Presence determines the "With Bank Info" stat count.
branch_acct_novarchar(20)Optional account number. Rendered in monospace font.
branch_acct_namevarcharOptional account holder name.
branch_created_atdatetimeCreation 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}.

POST / PUT body — branch_api.php JSON
{
  "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

branch_api.php — success response JSON
{
  "success": true,
  "data": { "branch_id": 14 }   // on create; null on update/delete
}

B Payload Fields

KeyTypeNotes
branch_namestringTrimmed. Required — validated before POST.
branch_codestringTrimmed and uppercased via .toUpperCase(). Required.
branch_addressstringTrimmed. Required.
branch_statestringThe state name text from the dropdown value. Not the numeric ID. Required.
branch_citystringThe city name text from the dropdown value. Not the numeric ID. Required.
branch_bankstringOptional. Empty string if not filled.
branch_acct_nostringOptional. Max 20 chars enforced by HTML maxlength attribute.
branch_acct_namestringOptional. Empty string if not filled.

JavaScript State Variables

VariableTypeDescription
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

FunctionTriggerDescription
init()Page loadCalls loadStates() then load() in sequence. States must be loaded before any form opens.
load()init + search + CRUD successFetches 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 buttonValidates 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 btnSends DELETE to branch_api.php?id={delId}. Disables button during request. On success: toast, close modal, reload.
openAdd()Add Branch buttonSets editId = null, calls clearForm(), opens #formModal.
openEdit(id)Edit icon / View modalSets editId, populates all form fields from branches array, calls preselectLocation(), opens #formModal.
openView(id)View iconBuilds HTML for the view card from branches array, wires "Edit Branch" button, opens #viewModal.
openDel(id)Delete iconSets delId and #delName, opens #delModal.
loadStates()init()Fetches location_api.php?type=states, stores in allStates, then calls populateStateSelect('fState').
onStateChange(sel)fState selectReads 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 inputSets 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.

OrderCheckField highlightedBehaviour
#1Name requiredfNameCalls hl('fName') and returns early if empty after trim.
#2Code requiredfCodeCalls hl('fCode') and returns early if empty after trim.
#3Address requiredfAddrCalls hl('fAddr') and returns early if empty after trim.
#4State requiredfStateCalls hl('fState') and returns early if no state selected.
#5City requiredfCityCalls hl('fCity') and returns early if no city selected.
Bank details are entirely optional 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.
Server-side validation 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.