# ABAS Event Management System (CodeIgniter 4)

A complete event management application: event creation, public registration with
auto-generated QR codes, walk-in / on-the-spot registration, QR-code attendance
scanning, volunteer registration, and one-click Excel (CSV) exports everywhere.

## What's included in this package

This package contains the **application layer** (`app/` folder) that you drop into
a fresh CodeIgniter 4 installation. It does not include the CodeIgniter framework
core itself (`system/`, `vendor/`) — that's installed via Composer in step 1 below,
which keeps this package small and lets you always start from the latest stable
CI4 release.

```
app/
  Config/Routes.php          <- all routes (public + admin)
  Config/Filters.php         <- registers the "adminauth" filter
  Controllers/               <- public controllers (home, registration, volunteer)
  Controllers/Admin/         <- admin controllers (auth, events, attendance, exports)
  Models/                    <- Event, Registration, Volunteer, AttendanceLog, Admin
  Database/Migrations/       <- creates all tables + a default admin user
  Filters/AdminAuth.php      <- protects /admin/* routes
  Views/                     <- admin dashboard + public registration/volunteer pages
env.example                  <- copy to .env and configure your database
```

## 1. Install CodeIgniter 4

```bash
composer create-project codeigniter4/appstarter abas-events
cd abas-events
```

## 2. Copy this package's app/ folder in

Copy the entire `app/` folder from this package **over** the `app/` folder CI4
created (merge — don't delete CI4's other default files like `Config/App.php`).

```bash
cp -r /path/to/this/package/app/* ./app/
cp /path/to/this/package/env.example ./.env
```

Edit `.env` with your real database credentials and site URL.

## 3. Create the database, then run migrations

```sql
CREATE DATABASE abas_events CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

```bash
php spark migrate
```

This creates all tables **and** a default admin login:

- URL: `/admin/login`
- Username: `admin`
- Password: `Admin@123`

**Change this password immediately** (update the `admins` table with a new
`password_hash()` value, or add an admin "change password" screen).

## 4. Run it

```bash
php spark serve
```

Visit:
- Public site: `http://localhost:8080/`
- Admin panel: `http://localhost:8080/admin/login`

For production, point your web server's document root at the `public/` folder
(standard CI4 deployment) rather than using `spark serve`.

---

## How each feature works

### Admin can add new events
`/admin/events` → "+ New Event". Stored in `events` table with a unique slug.

### Public event registration + QR code
`/event/{id}/register` is the public form. On submit, a row is created in
`registrations` with a short, unique `reg_code`. The success page renders a QR
code **client-side** (via the `qrcode.js` library, no server dependency needed)
encoding that `reg_code`, with a "Download QR Code" button. **Phone number is
enforced as the unique key per event** (`UNIQUE(event_id, phone)`) — if someone
re-submits with the same phone, they're shown their existing QR instead of a
duplicate registration.

### Walk-in ("on the day") registration
`/admin/events/{id}/walkin` — a fast form for staff at the venue door. Same
logic as public registration, but tagged `source = walkin`, and immediately
shows/downloads the QR code so it can be printed or shown on the attendee's
phone right there.

### Attendance taking with QR code
`/admin/events/{id}/attendance` opens a camera-based QR scanner (via the
`html5-qrcode` JS library) that reads the `reg_code` and calls
`/admin/attendance/scan` by AJAX to mark them checked in — instant visual
feedback, duplicate-scan protection, and an audit log of every scan
(`attendance_log` table). A manual phone-number lookup is included as a
fallback if a QR can't be scanned.

### Volunteer registration + QR
`/volunteer` is the public volunteer sign-up form (matches your two reference
screenshots). **Phone number is the unique key across the whole volunteer
table** — resubmitting with the same phone updates the existing record instead
of creating a duplicate. A QR code is generated the same way as event
registration.

### Excel downloads
Every admin list screen has a "Download Excel" button:
- `/admin/export/events`
- `/admin/export/registrations/{event_id}`
- `/admin/export/volunteers`
- `/admin/export/attendance/{event_id}`

These stream a `.csv` file (opens natively in Excel/Google Sheets, with a UTF-8
BOM so names in other scripts display correctly) directly from the database in
chunks of 500 rows, so exports stay fast and memory-light even with tens of
thousands of registrations. If you specifically need a native `.xlsx` file
(with formatting/multiple sheets), add `phpoffice/phpspreadsheet` via Composer
and swap `ExportController` to use it — the controller is structured so that's
a drop-in change.

---

## Performance notes for high registration volume

The app is built with heavy concurrent traffic in mind:

1. **DB-level uniqueness, not just app checks.** `UNIQUE(event_id, phone)` and
   `UNIQUE(phone)` constraints prevent duplicate registrations even under
   simultaneous double-submits/race conditions — the app catches the DB
   exception and gracefully redirects to the existing record instead of erroring.
2. **Indexes** on `phone`, `reg_code`, and `event_id` keep lookups (registration
   check, QR scan check-in) fast even with large tables.
3. **Active-events list is cached** for 60 seconds (`EventModel::activeEvents()`)
   since the homepage is read constantly but events rarely change.
4. **Exports stream in chunks** rather than loading the whole table into memory.
5. For real high-traffic events, also do the following (infra, not code):
   - Use MySQL/MariaDB (not SQLite) with `database.default.pConnect = true`.
   - Switch session/cache drivers to Redis (`session.driver`, `cache.handler`
     in `.env`) if running behind a load balancer with multiple app servers.
   - Enable PHP OPcache in production.
   - Put the app behind Nginx + PHP-FPM with a handful of workers, not the
     `php spark serve` dev server.
   - Consider a CDN in front of the public registration page's static assets.

---

## Notes / things you may want to extend

- Add a "change admin password" screen (currently must be done via SQL).
- Add pagination to the registrations/volunteers tables once they grow large
  (currently they load all rows — fine into the low thousands, but add
  `->paginate()` if you expect tens of thousands per event).
- Add a `.htaccess`/Nginx rewrite (CI4's default `public/index.php` handles
  this out of the box).
- CSRF protection is left off by default in `Config/Filters.php`'s `globals`
  array for simplicity of the fast walk-in/kiosk flow — turn it on
  (`'before' => ['csrf']`) if you'd like it globally, or apply selectively.
