# PRD — calendar-module

Evidence date: 2026-10-19. Revision: `master` `88c9d8d` (2020-10-07, Uriel Balela, "Calendar").

The repository has no product requirements document. `README.md` is the Create React App boilerplate. Every requirement below was recovered from the React components, the PHP scripts, and `php/fullcalender.sql`. Where the code and a normal reading of FullCalendar disagree, the discrepancy is marked **Requires Confirmation** because this investigation did not run the app.

Provenance labels: **Documented** (explicit text in the repo), **Implemented** (what the code does), **Inferred** (reasonable reading, not written as a requirement), **Unknown**.

## Product purpose

**Inferred.** A person can look at a calendar and add a titled event that is stored in a database and offered back to the same calendar.

No audience, tenant, or organisation is named. The HTML title remains "React App".

## Users and roles

No role, login, or session exists on the calendar path.

| Actor | Evidence | Provenance |
|---|---|---|
| Person with the React page open | Can select a range, type a title, and submit | **Implemented** (`Calendar.js`, `Modal.js`) |
| Caller who can reach the PHP URLs | Can read every event and insert an event. No credential is checked | **Implemented** (`fetchEvent.php`, `addEvent.php`) |
| Database user in `db_connection.php` | `root` on `localhost`, empty password, database name `login` | **Implemented** in that file. The file is not included by the scripts |

**Unknown / Requires Confirmation:** who was meant to use this, and whether it was only a local exercise on 2020-10-07.

## FR-001 — Open the calendar

**Requirement.** Loading the Create React App shows one calendar and nothing else.

**Who.** Any visitor to the page.

**Trigger.** Browser loads `src/index.js`, which renders `App`, which renders `Calendar`.

**Inputs.** None.

**Expected behaviour.** FullCalendar starts on `dayGridMonth`. The toolbar left side is previous, next, and today. The centre is the title. The right side offers month, week (`timeGridWeek`), and day (`timeGridDay`). The grid is `editable`, `selectable`, and `selectMirror`, and `dayMaxEvents` is true.

**Outputs.** The calendar DOM inside `.Calendar`.

**Validation.** None.

**Error.** `public/index.html` shows "You need to enable JavaScript to run this app" when JavaScript is off. No other empty or error state is coded.

**Dependencies.** `@fullcalendar/react` 5.3.1, dayGrid 5.3.2, timeGrid 5.3.1, interaction 5.3.1.

**Implementation.** Implemented.

**Evidence.** `src/index.js`, `src/App.js`, `src/Components/Calendar.js`.

**Confidence.** High for the render tree. The service worker is explicitly `unregister()` in `src/index.js`, so the page is not installed as an offline app.

## FR-002 — Choose a date range

**Requirement.** The person can select a calendar range, and can click a single date.

**Who.** The person on the page.

**Trigger.** FullCalendar `select` and `dateClick`.

**Inputs.** FullCalendar's select info and date-click info.

**Expected behaviour.**

- `select` stores the select info in component state (`eventClick`). It does not open the dialog by itself.
- `dateClick` stores `info.date` and that value is what mounts `Modal`.
- The dialog does not read `selectedDate`. On submit it reads `props.eventClick.startStr`, `endStr`, and `allDay`.

**Outputs.** Modal visibility, and the range later sent as `start` / `end`.

**Validation.** None. Any range FullCalendar reports is accepted.

**Error.** Initial `eventClick` state is `""`. If submit runs before a `select` has been stored, `eventObj.view` is read on that string. **Inferred** that this throws. Not executed.

**Dependencies.** `@fullcalendar/interaction`.

**Implementation.** Implemented as two separate handlers. Whether one mouse click fires both `select` and `dateClick` together is **Unknown / Requires Confirmation**.

**Evidence.** `Calendar.js` `handleDateSelect`, `Onchange`, `dateClick`, and the `{!!selectedDate && <Modal .../>}` condition. `Modal.js` `OnSubmit`.

**Confidence.** High for the split. Medium for the combined click.

## FR-003 — Capture an event title

**Requirement.** The dialog asks for a title and adds the event only when the title is non-empty.

**Who.** The person on the page.

**Trigger.** Submit of the form in `Modal.js`. The visible copy is "Please enter a new title for your event". The button label is "Add".

**Inputs.** A text field bound to React state `event`.

**Expected behaviour.**

- `preventDefault` stops a full page post.
- If `title` is truthy, `calendarApi.addEvent` is called with a string id from `createEventId`, the title, `start`, `end`, and `allDay` copied from the select info.
- If `title` is empty, nothing is added.
- Clicking the dark overlay calls `close`, which clears `selectedDate` and unmounts the dialog.
- Submit does not call `close`.

**Outputs.** A client-side FullCalendar event, which then triggers FR-004.

**Validation.** **Implemented** only as JavaScript truthiness. Spaces-only titles pass. There is no length cap in the browser. The database column is `varchar(255)`.

**Error.** No message is shown for an empty title. The form stays open.

**Dependencies.** `styled-components` for the overlay and the white panel. `react-bootstrap` is in `package.json` and is not imported.

**Implementation.** Implemented.

**Evidence.** `src/Components/Modal.js`.

**Confidence.** High.

**Note.** `createEventId` keeps `eventGuid` inside the component function, so each render starts again at `0`. The id is not sent to PHP. **Inferred.**

## FR-004 — Save the event

**Requirement.** After FullCalendar adds an event, the browser POSTs the title and range to PHP, which inserts one `event` row.

**Who.** The browser, then PHP. No user id is sent.

**Trigger.** FullCalendar `eventAdd` → `addEvent` in `Calendar.js`.

**Inputs.** JSON body `{ title, start, end }` taken from `addInfo.event.title`, `startStr`, and `endStr`. URL: `http://localhost/php-react/addEvent.php`.

**Expected behaviour in the browser.** Axios POST. The response is written to `console.log`. A failure is written to `console.log` as `"error"`. The UI does not show success or failure.

**Expected behaviour in PHP, if the includes resolve.**

1. `header.php` sets `Access-Control-Allow-Origin: *`, allows methods `POST, OPTIONS`, and sets `Content-Type` twice. The second call wins, so the response type is `application/x-www-form-urlencoded`.
2. `json_decode` of `php://input`.
3. `var_dump` of that object (the dump is part of the HTTP body).
4. `trim` of `title`, `start`, and `end`.
5. `Insertdata('event', ...)` builds `INSERT INTO event (eventTitle,dtDateStart,dtDateEnd,time,eventDescription) VALUES ('...','...','...','','')`, `print_r`s that SQL into the body, and runs it with `mysqli_query`.
6. There is no JSON success body.

**Validation.** PHP trims three strings. It does not check that they exist, that the end is after the start, or that the title is non-empty. `time` and `eventDescription` are always `""`.

**Error conditions.**

- Include failure: the script `require`s `dbconnect.php`. The tracked file is `db_connection.php`. **Implemented** names do not match, so a stock checkout fatals before the insert. **Requires Confirmation** whether the author's machine had a renamed copy.
- JSON that is missing `title`, `start`, or `end`: `trim($data->title)` (and the other properties) on null. On the PHP 7.2 named in the dump header this warns; the script still continues. Not executed here.
- `mysqli` error: `Insertdata` echoes `"Error: "` plus `mysqli_error`. It does not set an HTTP error status.
- OPTIONS preflight: the script does not return before reading the body. **Inferred** that a browser preflight (Axios JSON POST) runs the same insert path and fatals on a null body. **Requires Confirmation** by running it.
- Database name: connection file uses `login`. Dump comment says `fullcalender`. The dump has no `USE`. **Requires Confirmation** which database holds `event`.

**Outputs.** A new `event` row when the insert runs. The browser ignores the body.

**Dependencies.** Axios 0.20.0, PHP `mysqli`, table `event`, a web server that maps `/php-react/` onto these scripts. That mapping is not in the repository.

**Implementation.** Implemented as source. End-to-end success depends on files and a vhost that are not in git.

**Evidence.** `Calendar.js` `addEvent`; `php/addEvent.php`; `php/header.php`; `php/functions.php` `Insertdata`; `php/db_connection.php`; `php/fullcalender.sql`.

**Confidence.** High for the source. Low for a working local stack, because the include name and the URL path are not satisfied by the tree.

## FR-005 — List stored events

**Requirement.** The calendar asks PHP for events, and PHP returns every row as JSON.

**Who.** The browser on load, via the FullCalendar `events` prop. PHP does not filter by user or date.

**Trigger.** FullCalendar requests the event source. The function ignores the range argument FullCalendar passes.

**Inputs.** GET `http://localhost/php-react/fetchEvent.php`. No query string.

**Expected behaviour in PHP.** `SELECT * FROM event`. Each row becomes `{ title: eventTitle, start: dtDateStart, end: dtDateEnd }`. `eventID`, `time`, and `eventDescription` are not copied. The script echoes `json_encode` of that list. If the query fails, the script echoes nothing.

**Expected behaviour in the browser.** `fetchEvent` returns `response.data` from Axios. It does not call a success callback. FullCalendar 5.3's event-source function is defined to receive `(arg, successCallback, failureCallback)` and to deliver events through `successCallback`. **Inferred** that the return value is discarded and the grid stays empty. **Requires Confirmation** by running `@fullcalendar/react` 5.3.1.

**Validation.** None. Empty strings in the dump are returned as empty titles.

**Error.** Axios failure is `console.log` only. PHP include failure is the same missing `dbconnect.php` as FR-004. `header.php` allows `POST, OPTIONS` and does not list `GET`. A simple GET may still be readable cross-origin. **Requires Confirmation.**

**Outputs.** A JSON array when the query runs.

**Dependencies.** Same PHP host and database as FR-004.

**Implementation.** The PHP mapping is implemented. The React hand-off does not follow the FullCalendar 5 callback contract.

**Evidence.** `Calendar.js` `fetchEvent` and `events={fetchEvent}`; `php/fetchEvent.php`.

**Confidence.** High for each file. Medium for the combined result.

## FR-006 — Store the event row

**Requirement.** An event is one row in `event`.

**Who.** `addEvent.php` through `Insertdata`. The dump shows the shape that was used on 2020-10-07.

**Trigger.** A successful insert.

**Inputs.** See FR-004.

**Expected behaviour.** Columns, from `php/fullcalender.sql`:

| Column | Definition | Written by `addEvent.php` |
|---|---|---|
| `eventID` | `int(11)` primary key, auto-increment | Left to the database |
| `eventTitle` | `varchar(255) NOT NULL` | Trimmed title. Empty string is allowed by the column and by PHP |
| `dtDateStart` | `varchar(255)` NULL | Trimmed `start` string. Not a SQL `DATE` |
| `dtDateEnd` | `varchar(255)` NULL | Trimmed `end` string |
| `time` | `time` NULL | Always `""` from PHP. Dump rows show `00:00:00` |
| `eventDescription` | `text` NULL | Always `""` |

Engine InnoDB, charset `utf8mb4`. The dump's `AUTO_INCREMENT` is 17. Thirteen rows are inserted, with ids 2, 4, and 6 through 16. Several titles are empty. Dates in the dump fall in October 2020. Descriptions are empty.

**Validation.** Primary key on `eventID` only. No unique title, no date check constraint.

**Error.** Not specified.

**Outputs.** The row. The sample rows are personal test data from the dump afternoon. This PRD does not repeat those titles.

**Implementation.** Implemented as a dump plus the insert statement. The dump is a phpMyAdmin export (version 5.0.1, host `127.0.0.1`, generated 2020-10-07 16:41, server MariaDB 10.4.11, PHP 7.2.0).

**Evidence.** `php/fullcalender.sql`, `php/addEvent.php`.

**Confidence.** High for the file contents. The live database, if one still exists, was not inspected.

## FR-007 — Change or remove an event

**Requirement.** None is implemented.

**Documented remnant.** `Calendar.js` contains a commented `handleEventClick` that would `confirm` and then `clickInfo.event.remove()`. It is not passed to FullCalendar. `eventClick` on the component is the select-info state, not this handler.

**Implemented adjacent behaviour.** `editable={true}` lets FullCalendar drag and resize on the client. No `eventChange`, `eventDrop`, or `eventResize` prop posts the new range. There is no update or delete PHP script. `UpdateTable` in `functions.php` builds an update string and does not query, and it is not called.

**Requires Confirmation.** Whether drag-edit and delete were intended next steps or experiments left in the file.

**Confidence.** High that persistence of edits is absent.

## Workflows

### Add a titled event

1. Person opens the React app (`yarn start` serves port 3000, per the README).
2. Person clicks a date. `dateClick` opens the modal.
3. A select, if it has run, holds `startStr` / `endStr`.
4. Person types a title and presses Add.
5. FullCalendar adds the event on the client and calls `addEvent`.
6. Axios POSTs to `http://localhost/php-react/addEvent.php`.
7. PHP inserts into `event` when `dbconnect.php` and the table exist.

The modal stays open until the overlay is clicked. The grid is not told to refetch.

### Show existing events

1. FullCalendar mounts and calls `fetchEvent`.
2. Axios GETs `fetchEvent.php`.
3. PHP returns every row's title and range, or fatals on the missing include.
4. The function returns that array to its caller. It does not pass it to FullCalendar's success callback (FR-005).

### Paths that are not workflows

- Sign-in. No screen. `functions.php` `redirectToLoginPage` sends `Location: ../index.php`, and nothing calls it. There is no `index.php`.
- Edit, delete, or describe an event.
- Deploy. The README links to the Create React App deployment page and does not name a host.

## Integrations

| System | Purpose | Trigger | Direction | Auth | Response | Failure |
|---|---|---|---|---|---|---|
| PHP on `localhost` path `/php-react/` | Insert and list events | `eventAdd`; FullCalendar event source | Browser → PHP → MySQL | None. CORS `*` | Insert: `var_dump` plus SQL text. List: JSON array | Console log in the browser. PHP echoes `mysqli_error` or fatals. HTTP status stays success unless PHP fatals |
| MariaDB / MySQL | Store `event` | `mysqli_query` | PHP → database | `root` / empty password in `db_connection.php`, database `login`. File is not the one included | Insert result boolean. Select rows | Echo `mysqli_error` from `Insertdata`. `fetchEvent.php` stays silent |
| npm registry | Install the React app | `yarn` or `npm install` (both lockfiles are committed) | Dev machine → registry | None in repo | Lockfiles | Not described |

No email, queue, payment, or third-party calendar API is called. The comment "email function still need to be added" sits on the unused user helpers.

## Constraints

- **Documented.** README: development server is `http://localhost:3000`. Scripts are `yarn start`, `yarn test`, `yarn build`, `yarn eject`.
- **Implemented.** Event dates are stored as strings, not SQL dates. Time and description are not collected.
- **Implemented.** All events are global. The list query has no `WHERE`.
- **Implemented.** Insert values are concatenated into SQL. A title can close the quote and change the statement.
- **Implemented.** `addEvent.php` writes the decoded payload and the SQL to the response.
- **Inferred.** The React origin (port 3000) and the PHP origin (port 80, `/php-react/`) must both be up. Nothing in git starts the PHP host.
- **Unknown.** Production URL, backup, retention, and who may see another person's events.

## Requirements that the tree does not support

These are not written requirements. They are absent, and a reader might assume a calendar has them.

| Topic | What the tree shows |
|---|---|
| Accounts | User helpers in `functions.php` are unused and point at a `user` table that the SQL dump does not create |
| Per-person calendars | No owner column |
| Description and clock time | Columns exist; the form and the insert do not fill them |
| Edit and delete | Not wired |
| Reliable reload of saved events | Fetch does not use the FullCalendar 5 success callback |
| Tests of the calendar | `App.test.js` still looks for the text "learn react", which `App.js` does not render |
| Hardening | No auth, no CSRF token, CORS `*`, empty database password |

## Assumptions that must not be treated as fact

- That a click both selects a range and opens the modal with that range.
- That FullCalendar 5.3 paints events returned as a promise from the `events` function.
- That the author's Apache/nginx directory was named `php-react` and contained these PHP files under that name.
- That database `login` contains table `event`.
- That the module is part of a product called Unity or anything else. The word does not appear in this repository.
