SitePlex Feature Showcase

This page showcases some of the features I’ve worked on for SitePlex.

Feature

User Search

<!-- Search input -->
<div class="search">
  <input
    type="search"
    id="searchQuery"
    placeholder="Search by name, email, or role..."
  />
</div>

<table id="userTable" class="table">
  <tr>
    <th>Name</th>
    <th>Email</th>
    <th>Role</th>
  </tr>

  <!-- User rows here -->
</table>
          

What it does

Adds an instant, client-side search bar for the user table. As the user types, rows are filtered by name, email, or role.

Uses an input event listener and toggles row visibility (`display: none`) for non-matches. It works cleanly without external libraries and keeps the UI responsive.

Feature

Add QR Code Sign in and Forms

<!-- Sign-in QR shown separately -->
<a href="{{ route('qrcode.signin.show', ['uuid' => $signInQr->uuid]) }}" target="_blank">
  Sign In QR Code
</a>

<!-- Form QRs -->
<a href="{{ route('qrcode.submit', ['uuid' => $qrCode->uuid]) }}" target="_blank">
  {{ $qrCode->form->title }}
</a>

<!-- Print QR PDF -->
<a href="{{ route('qrcode.pdf', ['uuid' => $qrCode->uuid]) }}" target="_blank">
  Print QR Code
</a>
          

What it does

This feature adds QR code-based sign-in and form submission through unique URLs for each project and form.

QR codes are generated in a queued job and stored on S3. The job restores previously soft-deleted QR codes instead of creating duplicates.

Feature

REST API for QR Codes

SitePlex API response example
class QrCodeApiController extends ApiController
{
    // GET /qrcodes/{project}?api_token=...&from_date=Y-m-d H:i:s
    public function index(Project $project, Request $request)
    {
        $validator = Validator::make($request->all(), [
            'from_date' => 'nullable|date_format:Y-m-d H:i:s',
        ]);

        if ($validator->fails()) {
            return $this->respondBadRequest($validator->messages());
        }

        $qrCodes = QrCode::where('project_id', $project->id)
            // Keep sign-in QR (form_id null) + only non-deleted forms
            ->where(function ($q) {
                $q->whereNull('form_id')
                  ->orWhereHas('form', function ($sub) {
                      $sub->whereNull('deleted_at');
                  });
            })

            ->get(['form_id', 'project_id', 'uuid', 'type', 'updated_at']);

        return $this->respondOK(
            $qrCodes->map(function ($qr) {
                return [
                    'form_id'    => $qr->form_id,              // null for sign-in
                    'project_id' => $qr->project_id,
                    'uuid'       => $qr->uuid,
                    'type'       => $qr->type,                // 1=form, 2=sign-in
                    'updated_at' => $qr->updated_at->format('Y-m-d H:i:s'),
                ];
            })
        );
    }
}
          

What it does

Implemented a REST-style endpoint to list a project’s QR codes (forms + sign-in) and support incremental sync via from_date. This powers the mobile app by keeping QR links up to date without re-downloading everything.

The endpoint validates request params, filters out QR codes linked to deleted forms, and always includes the project’s Sign-In QR (stored with a null form_id).

Feature

QR Form Submission with Eloquent

Media preview
<!-- qrcode form submit -->
<form method="POST" action="{{ route('qrcode.submit.store', $qrCode->uuid) }}">
  @csrf

  @foreach($formVersion->sections as $section)
    <h4>{{ $section->title }}</h4>

    @foreach($section->fields as $field)
      <label for="field-{{ $field->id }}">{{ $field->label }}</label>
      <input
        id="field-{{ $field->id }}"
        name="fields[{{ $field->id }}][value]"
        value="{{ old("fields.$field->id.value") }}"
      />
    @endforeach
  @endforeach

  <label>
    <input type="checkbox" name="draft" value="1"> Save as draft
  </label>

  <button type="submit">Submit</button>
</form>

          

What it does

Built a QR form submission flow using Eloquent to load the correct published form version, collect dynamic field input, and save the submission along with its related answers.

Used relationships, eager loading, and a database transaction to avoid unnecessary queries and maintain data consistency for both draft and final submissions.