<h1>Calendar &amp; Events — <code>CalendarService</code> + Designer</h1>
<p>
    The <strong>Calendar</strong> is a standalone event-management app (like Google Calendar) that <?= htmlspecialchars(\PpPhp\Core\Brand::name()) ?>
    can add to any generated application. It <strong>provisions its own tables</strong>, lets users create
    and share events, schedule meetings, invite people and groups, set reminders, and import/export iCal —
    all without a business table. This guide covers how to <strong>enable</strong> it, <strong>customize</strong>
    it in the Designer, and call its <strong>PHP API</strong> from your own code.
</p>

<h2>Contents</h2>
<ul>
    <li><a href="#enable">Enabling the Calendar</a></li>
    <li><a href="#designer">Customizing in the Designer</a></li>
    <li><a href="#regions">Region reference (Header / Toolbar / Sidebar / Grid / Footer)</a></li>
    <li><a href="#tables">Tables &amp; the visibility model</a></li>
    <li><a href="#service">Server API — <code>CalendarService</code></a></li>
    <li><a href="#reminders">Reminders &amp; notifications</a></li>
    <li><a href="#ical">iCal import / export &amp; recurrence</a></li>
    <li><a href="#json">The generated JSON endpoint</a></li>
    <li><a href="#frontend">Front-end — <code>PPCalendarApp.mount()</code></a></li>
    <li><a href="#examples">Recipes</a></li>
</ul>

<!-- =========================================== -->
<h2 id="enable">Enabling the Calendar</h2>
<p>
    Open the <strong>Tables</strong> module and click <strong>Create Calendar</strong>. Toggle
    <em>Enable the Calendar app in this project</em> and pick your defaults (default view, first day of
    week, menu label, personal calendars, in-app notifications, email reminders), then
    <strong>Save &amp; enable</strong>. On the next <strong>Build</strong>, the calendar is generated at
    <code>generated/calendar/index.php</code> and a menu link is added.
</p>
<p>
    The enable switch lives only in this modal — it is what turns the calendar on for the project. Everything
    about how it <em>looks and behaves</em> is edited in the Designer (below), which overrides these defaults.
    The calendar creates its tables automatically the first time a signed-in user opens it.
</p>

<!-- =========================================== -->
<h2 id="designer">Customizing in the Designer</h2>
<p>
    Open <strong>Designer → Common Pages → Calendar</strong>. What you see on the canvas is exactly what the
    build generates. The calendar is one <em>smart component</em> whose regions are individually clickable:
    click the <strong>Header</strong>, <strong>Toolbar</strong>, <strong>Sidebar</strong>, <strong>Grid</strong>
    or <strong>Footer</strong> and the Properties panel focuses on that region. Click the calendar background
    (or use the component chip) to edit all settings at once.
</p>
<p>
    Every control here provably changes the generated app — the Designer only exposes settings the live
    calendar can honour, so <em>what you design is what you get</em>. To start over, use
    <strong>Reset to default</strong> on the page; it restores the standard template.
</p>

<h3 id="regions">Region reference</h3>
<table class="pp-help-table">
    <thead><tr><th>Region</th><th>Settings</th></tr></thead>
    <tbody>
        <tr><td><strong>Header</strong></td><td>Heading, subtitle, menu label, alignment, accent colour, and heading <em>typography</em> (font, size, colour).</td></tr>
        <tr><td><strong>Toolbar</strong></td><td>Default view; show/hide each view (Week, Day, Agenda, Year — Month is always on); Today button; prev/next arrows; Import/Export buttons; the “New event” button and its label.</td></tr>
        <tr><td><strong>Sidebar</strong></td><td>Show/hide the whole side panel, and per-section toggles: Create button, mini-month navigator, Calendars list, Categories, Upcoming events.</td></tr>
        <tr><td><strong>Grid</strong></td><td>Week-start day, weekend shading, Today colour, grid-line colour.</td></tr>
        <tr><td><strong>Footer</strong></td><td>Show an optional caption line under the calendar, with custom text.</td></tr>
        <tr><td><strong>Appearance</strong></td><td>Base font for the whole calendar app.</td></tr>
        <tr><td><strong>Notifications</strong></td><td>Personal calendars, in-app notifications, email reminders.</td></tr>
    </tbody>
</table>
<p class="pp-help-note">
    <strong>Fonts &amp; colours.</strong> Font pickers use the app’s bundled web fonts (add more via the local
    fonts pipeline). Colour and size fields accept CSS values (e.g. <code>#ef4444</code>, <code>1.8rem</code>);
    they are sanitised at build time before being written into the page.
</p>

<!-- =========================================== -->
<h2 id="tables">Tables &amp; the visibility model</h2>
<p>The calendar self-provisions five <code>pp_</code> tables (created on first run, healed on upgrade):</p>
<ul>
    <li><code>pp_calendars</code> — a calendar owns events; each user gets a personal “My Calendar”.</li>
    <li><code>pp_events</code> — title, description, location, start/end, all-day, colour, icon, image, visibility, recurrence.</li>
    <li><code>pp_event_shares</code> — share an event or calendar with <em>everyone</em>, a <em>group</em>, or a <em>user</em> (view / edit).</li>
    <li><code>pp_event_attendees</code> — invitees and their RSVP (pending / accepted / declined / tentative).</li>
    <li><code>pp_event_reminders</code> — minutes-before + method (popup / in-app / email).</li>
</ul>
<p>
    <strong>Who can see an event?</strong> A user sees an event if they own it, OR it is public, OR it is shared
    to them (directly, via a group, or to everyone), OR they are an attendee — and admins see all. Editing
    requires ownership, an edit-share, or admin.
</p>

<!-- =========================================== -->
<h2 id="service">Server API — <code>CalendarService</code></h2>
<p>
    All logic lives in <code>core/Calendar/</code> (shipped into every build), so you can call it from your
    own event handlers, cron tasks or custom pages. Resolve the current user’s context first:
</p>
<pre><code>use PpPhp\Core\Calendar\CalendarSchema;
use PpPhp\Core\Calendar\CalendarService;

CalendarSchema::ensure();                 // create/heal tables (safe to call anytime)
$ctx = CalendarService::contextFromApp(); // ['uid' =&gt; int, 'groups' =&gt; int[], 'isAdmin' =&gt; bool]</code></pre>

<h3>Reading</h3>
<pre><code>// Calendars the user can see (always includes their personal calendar)
$cals = CalendarService::calendars($ctx);

// Events visible to the user in a date window (recurrence expanded to occurrences)
$events = CalendarService::events($ctx, '2026-07-01', '2026-07-31');

// Optionally restrict to specific calendar ids
$events = CalendarService::events($ctx, $from, $to, [1, 4]);

// One event with its shares, attendees and reminders
$ev = CalendarService::getEvent($ctx, $eventId);</code></pre>

<h3>Writing</h3>
<pre><code>$id = CalendarService::createEvent($ctx, [
    'title'      =&gt; 'Sprint review',
    'starts_at'  =&gt; '2026-07-15 15:00',
    'ends_at'    =&gt; '2026-07-15 16:00',
    'location'   =&gt; 'Room 2',
    'color'      =&gt; '#2563eb',
    'visibility' =&gt; 'groups',                 // inherit|private|public|groups|users
    'rrule'      =&gt; 'FREQ=WEEKLY;BYDAY=TU',   // optional recurrence
    'reminders'  =&gt; [['minutes' =&gt; 15, 'method' =&gt; 'notify']],
    'shares'     =&gt; [['principal' =&gt; 'group', 'id' =&gt; 3, 'permission' =&gt; 'view']],
    'attendees'  =&gt; [7, 12],
]);

CalendarService::updateEvent($ctx, $id, ['title' =&gt; 'Sprint review (moved)']);
CalendarService::reschedule($ctx, $id, '2026-07-16 15:00', '2026-07-16 16:00');
CalendarService::setRsvp($ctx, $id, 'accepted');
CalendarService::deleteEvent($ctx, $id);</code></pre>
<p class="pp-help-note">Every write is permission-checked against <code>$ctx</code>. Ids are validated; user text is parameterised.</p>

<!-- =========================================== -->
<h2 id="reminders">Reminders &amp; notifications</h2>
<p>
    Events carry reminders with a <em>method</em>: <code>popup</code> (a browser alert, client-side),
    <code>notify</code> (the in-app notification bell) or <code>email</code>. Enable the delivery paths in the
    Designer’s <strong>Notifications</strong> region (<em>In-app notifications</em> / <em>Email reminders</em>).
    When either is on, the build adds a <code>calendar-reminders</code> cron task that dispatches due reminders:
</p>
<pre><code>// Run by the generated cron once a minute; safe to call yourself.
CalendarService::dispatchReminders();</code></pre>
<p>
    You can also <strong>circulate</strong> a new event to its audience (attendees + share targets) on creation:
</p>
<pre><code>// delivery: 'notify' | 'email' | 'both' | 'none'
CalendarService::circulate($ctx, $eventId, 'both');</code></pre>
<p>
    In-app notifications are written to the app’s own notifications table (Misc → Advanced → Notifications), so
    they appear in the same bell your other features use.
</p>

<!-- =========================================== -->
<h2 id="ical">iCal import / export &amp; recurrence</h2>
<pre><code>use PpPhp\Core\Calendar\CalendarIcs;

// Export visible events to an .ics string
$ics = CalendarIcs::export(CalendarService::events($ctx, $from, $to));

// Parse an uploaded .ics into event arrays (feed to createEvent)
$rows = CalendarIcs::parse($uploadedIcsString);</code></pre>
<p>
    Recurrence uses standard RRULE (<code>FREQ</code> = DAILY / WEEKLY / MONTHLY / YEARLY, plus
    <code>INTERVAL</code>, <code>COUNT</code>, <code>UNTIL</code>, <code>BYDAY</code>). The service stores one
    master event and expands occurrences on read via <code>CalendarRecurrence</code>.
</p>

<!-- =========================================== -->
<h2 id="json">The generated JSON endpoint</h2>
<p>
    The built calendar page (<code>generated/calendar/index.php</code>) is both the page shell and a JSON API,
    dispatched by <code>?action=</code>. All writes are POST and CSRF-guarded (token in the JSON body or the
    <code>X-CSRF-Token</code> header). Actions:
</p>
<table class="pp-help-table">
    <thead><tr><th>Action</th><th>Purpose</th></tr></thead>
    <tbody>
        <tr><td><code>bootstrap</code></td><td>Initial payload (calendars, principals, settings).</td></tr>
        <tr><td><code>events</code></td><td>Events in a <code>from</code>/<code>to</code> window.</td></tr>
        <tr><td><code>event</code></td><td>One event with shares/attendees/reminders.</td></tr>
        <tr><td><code>create_event</code> · <code>update_event</code> · <code>delete_event</code></td><td>CRUD.</td></tr>
        <tr><td><code>reschedule</code></td><td>Drag-move / resize.</td></tr>
        <tr><td><code>create_calendar</code></td><td>New personal calendar.</td></tr>
        <tr><td><code>rsvp</code></td><td>Respond to an invitation.</td></tr>
        <tr><td><code>principals</code></td><td>Users + groups for invite/share pickers.</td></tr>
        <tr><td><code>import</code> · <code>export</code></td><td>iCal in / out.</td></tr>
    </tbody>
</table>

<!-- =========================================== -->
<h2 id="frontend">Front-end — <code>PPCalendarApp.mount()</code></h2>
<p>
    The UI is a single self-contained asset (<code>assets/vendor/ppcal/pp-calendar.js</code>, no CDN). The build
    mounts it with the options the Designer produced:
</p>
<pre><code>PPCalendarApp.mount(document.getElementById('pp-calendar-app'), {
    base: '.../generated/calendar/index.php',
    token: '&lt;csrf&gt;',
    defaultView: 'month',        // month|week|day|agenda|year
    firstDay: 0,                 // 0 = Sunday, 1 = Monday
    views: ['month','week','day','agenda','year'],  // which view buttons show
    showToday: true, showNav: true, showIcs: true,
    showNewEvent: true, newEventLabel: 'New event',
    weekendShading: false,
    showSidebar: true,           // + sideCreate / sideMiniMonth / sideCalendars / sideCategories / sideUpcoming
    appReminders: true, emailReminders: false, notifEnabled: true,
    allowPersonal: true
});</code></pre>
<p>Header typography, accent, Today colour, grid-line colour and base font are applied as scoped CSS / CSS
    variables on the page — you don’t pass them here; the Designer bakes them into the shell.</p>

<!-- =========================================== -->
<h2 id="examples">Recipes</h2>
<p><strong>Seed an event from an order handler:</strong></p>
<pre><code>$ctx = CalendarService::contextFromApp();
CalendarService::createEvent($ctx, [
    'title'     =&gt; 'Deliver order #' . $orderId,
    'starts_at' =&gt; $dueDate . ' 09:00',
    'all_day'   =&gt; 0,
    'color'     =&gt; '#16a34a',
    'reminders' =&gt; [['minutes' =&gt; 60, 'method' =&gt; 'email']],
]);</code></pre>

<p><strong>A read-only, embedded calendar</strong> (no sidebar, month only): in the Designer set the
    <em>Sidebar → Show side panel</em> off, and in the <em>Toolbar</em> turn off every view except Month and
    hide the “New event” button. Rebuild — the generated page matches exactly.</p>

<p><strong>Brand the calendar:</strong> set the <em>Header</em> accent to your brand colour, choose a heading
    font and size, and (optionally) a base font under <em>Appearance</em>. The mini-month “today”, the primary
    buttons and event chips all follow the accent.</p>

<p class="pp-help-note">
    See also: <a href="../Notification/">Notifications</a> (the bell reminders use),
    <a href="../Email/">Email</a> (email reminders), and the Designer’s built-in
    <em>Common Pages → Calendar</em> live preview.
</p>
