Skip to content

Fix calendar: MySQL 8.0 anniversary query crash and Quill toolbar duplication#8128

Merged
DawoudIO merged 9 commits intomasterfrom
copilot/fix-calendar-issue-churchcrm
Mar 3, 2026
Merged

Fix calendar: MySQL 8.0 anniversary query crash and Quill toolbar duplication#8128
DawoudIO merged 9 commits intomasterfrom
copilot/fix-calendar-issue-churchcrm

Conversation

Copy link
Contributor

Copilot AI commented Mar 2, 2026

  • Fix MySQL 8.0 DATE filtering in AnniversariesCalendar.php
  • Fix Quill editor toolbar duplication (mount-once useEffect + onChangeRef)
  • Add Cypress regression tests
  • Fix CI failure: replace eslint-disable-next-line with biome-ignore comment in QuillEditor.tsx
  • Add value sync effect using clipboard.convert + setContents (proper Quill API, preserves undo history vs raw innerHTML)
  • Improve Cypress test: wait for at least one toolbar before asserting exact count of 2
Original prompt

This section details on the original issue you should resolve

<issue_title>Bug: Churchcrm 7.0.0 calendar issue</issue_title>
<issue_description>Hello @DawoudIO ,

Quick, friendly bug report

Thanks for helping us improve ChurchCRM. Clear details help us triage and fix bugs faster.

Please share:

However, when I visit Calendar-> Select a specific date by clicking on it -> Enter event title or select any option from dropdown i.e event type or pinned calendars the toolbox keeps up adding another toolboxes and this slows down the browser.

Also cannot enter any description in Description field or text in 'Text' field or save any event.

  • Steps to reproduce:

  • Install Churchcrm

  • Go to Calendar -> Click on Specific date -> Try to add event title or select anything from dropdowns .

  • Toolbox(Editors) keep on expanding and slow down the browser

  • What you expected: What should have happened instead

  • Churchcrm event calendar should have worked properly.

If you're an admin and can access the app, the fastest way to collect debug info:

Admin → System Maintenance → System Logs (or click the debug icon). Attach logs/screenshots if available — please redact private data.

Following is the app.log under logs folder :

{"message":"Uncaught exception: Unable to execute statement [SELECT family_fam.fam_ID, family_fam.fam_Name, family_fam.fam_Address1, family_fam.fam_Address2, family_fam.fam_City, family_fam.fam_State, family_fam.fam_Zip, family_fam.fam_Country, family_fam.fam_HomePhone, family_fam.fam_Email, family_fam.fam_WeddingDate, family_fam.fam_DateEntered, family_fam.fam_DateLastEdited, family_fam.fam_EnteredBy, family_fam.fam_EditedBy, family_fam.fam_scanCheck, family_fam.fam_scanCredit, family_fam.fam_SendNewsLetter, family_fam.fam_DateDeactivated, family_fam.fam_Latitude, family_fam.fam_Longitude, family_fam.fam_Envelope FROM family_fam WHERE family_fam.fam_WeddingDate<>:p1]","context":{"exception":{"class":"Propel\\Runtime\\ActiveQuery\\QueryExecutor\\QueryExecutionException","message":"Unable to execute statement [SELECT family_fam.fam_ID, family_fam.fam_Name, family_fam.fam_Address1, family_fam.fam_Address2, family_fam.fam_City, family_fam.fam_State, family_fam.fam_Zip, family_fam.fam_Country, family_fam.fam_HomePhone, family_fam.fam_Email, family_fam.fam_WeddingDate, family_fam.fam_DateEntered, family_fam.fam_DateLastEdited, family_fam.fam_EnteredBy, family_fam.fam_EditedBy, family_fam.fam_scanCheck, family_fam.fam_scanCredit, family_fam.fam_SendNewsLetter, family_fam.fam_DateDeactivated, family_fam.fam_Latitude, family_fam.fam_Longitude, family_fam.fam_Envelope FROM family_fam WHERE family_fam.fam_WeddingDate<>:p1]","code":0,"file":"/{{PATH}}/{{TO}}/{{CHURCHCRM}}/vendor/perplorm/perpl/src/Propel/Runtime/ActiveQuery/QueryExecutor/AbstractQueryExecutor.php:148","previous":{"class":"PDOException","message":"SQLSTATE[HY000]: General error: 1525 Incorrect DATE value: ''","code":0,"file":"/{{PATH}}/{{TO}}/{{CHURCHCRM}}/vendor/perplorm/perpl/src/Propel/Runtime/Connection/StatementWrapper.php:224"}},"method":"GET","path":"/{{RELATIVEURL}}/api/systemcalendars/1/fullcalendar","query":"_=1772455599736&start=2026-03-01T00%3A00%3A00&end=2026-04-12T00%3A00%3A00&timeZone=America%2FNew_York","ip":"{{IPADDRESS}}","user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:148.0) Gecko/20100101 Firefox/148.0"},"level":400,"level_name":"ERROR","channel":"defaultLogger","datetime":"2026-03-02T07:46:39.254766-05:00","extra":{"url":"/{{RELATIVEURL}}/api/systemcalendars/1/fullcalendar?_=1772455599736&start=2026-03-01T00%3A00%3A00&end=2026-04-12T00%3A00%3A00&timeZone=America%2FNew_York","remote_ip":"{{IPADDRESS}}","correlation_id":"{{ID}}"}}

Screenshots help: If you can capture a quick image, it speeds up our investigation.

Following is the screenshot for the same :

Image

Comments on the Issue (you are @copilot in this section)


🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

- AnniversariesCalendar.php: replace filterByWeddingdate('', NOT_EQUAL)
  with filterByWeddingdate(null, ISNOTNULL) to avoid MySQL 8.0 strict-mode
  rejection of empty string as a DATE parameter (fixes SQLSTATE HY000 / 1525)
- QuillEditor.tsx: initialize Quill only once on mount (empty dep array) and
  use a useRef for the onChange callback so the text-change handler always has
  the latest callback without triggering re-initialization, which was creating
  a duplicate toolbar on every parent re-render

Co-authored-by: DawoudIO <554959+DawoudIO@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix calendar functionality in ChurchCRM 7.0.0 Fix calendar: MySQL 8.0 anniversary query crash and Quill toolbar duplication Mar 2, 2026
Copy link
Contributor

@DawoudIO DawoudIO left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot we need a light testing of these bugs

- Add private.admin.anniversaries.calendar.spec.js: API tests for
  /api/systemcalendars/1/fullcalendar and /events endpoints, verifying
  the MySQL 8.0 DATE comparison fix returns 200 with valid anniversary
  events (regression for SQLSTATE HY000: 1525)
- Update standard.calendar.spec.js: add 'Quill toolbars do not duplicate
  after parent re-renders' UI test that opens the event form, types a title
  and interacts with the Event Type dropdown, then asserts exactly 2
  .ql-toolbar elements remain (regression for toolbar duplication bug)

Co-authored-by: DawoudIO <554959+DawoudIO@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 2, 2026

@copilot we need a light testing of these bugs

Added two Cypress regression tests in commit 0b73cf4:

1. API testcypress/e2e/api/private/admin/private.admin.anniversaries.calendar.spec.js
Hits GET /api/systemcalendars/1/fullcalendar and /api/systemcalendars/1/events, asserting a 200 response and valid anniversary-event structure. This directly covers the MySQL 8.0 SQLSTATE[HY000]: 1525 fix — if the old empty-string DATE comparison were still in place, the endpoint would return a 500.

2. UI testcypress/e2e/ui/events/standard.calendar.spec.js
New test "Quill toolbars do not duplicate after parent re-renders": opens the new-event modal, then types a title and clicks the Event Type dropdown (both actions trigger a React re-render), and asserts that .ql-toolbar count remains exactly 2 throughout. Before the useEffect fix, each interaction appended another toolbar to the DOM (as shown in the screenshot above).

@DawoudIO DawoudIO added this to the 7.0.1 milestone Mar 2, 2026
@DawoudIO DawoudIO added the bug label Mar 2, 2026
@DawoudIO
Copy link
Contributor

DawoudIO commented Mar 2, 2026

@copilot please review other ORM issues like this in the code base

@DawoudIO DawoudIO marked this pull request as ready for review March 2, 2026 21:22
@DawoudIO DawoudIO requested a review from a team as a code owner March 2, 2026 21:22
@DawoudIO DawoudIO requested review from DAcodedBEAT, MrClever, bigtigerku, Copilot, grayeul and respencer and removed request for a team March 2, 2026 21:22
Copy link
Contributor Author

Copilot AI commented Mar 2, 2026

@copilot please review other ORM issues like this in the code base

Scanned the full codebase for the same pattern — comparing a typed column (DATE/DATETIME) against an empty string ''.

Result: no other instances found. Here's a summary of what exists:

File Pattern Column type Status
AnniversariesCalendar.php filterByWeddingdate('', NOT_EQUAL) DATE ✅ Fixed in this PR (ISNOTNULL)
ConfirmReportEmail.php filterByEmail('', NOT_EQUAL) VARCHAR(50) ✅ Safe — comparing '' against a VARCHAR is valid in MySQL 8 strict mode
PersonService.php filterByEmail('', NOT_EQUAL) VARCHAR(50) ✅ Safe — same as above; also intentionally excludes empty-string emails
people-families.php filterByWeddingdate(null, NOT_EQUAL) DATE ✅ Correct — null with NOT_EQUAL generates IS NOT NULL in Propel
EventsMenuItems.php filterByWeddingdate(null, NOT_EQUAL) DATE ✅ Correct — same
BirthdaysCalendar.php filterByBirthDay(0, GREATER_THAN) TINYINT ✅ Correct — numeric comparison

The AnniversariesCalendar.php bug was unique because it was the only place where an empty string was passed as a parameter to a DATE-typed column, which MySQL 8.0 strict mode rejects (SQLSTATE[HY000]: 1525). All other DATE-column queries already use null (or numeric comparisons), which generate correct SQL.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes two regressions affecting the calendar event editor: the Anniversaries system calendar query crashing under MySQL 8 strict mode, and Quill editor toolbars duplicating on React re-renders.

Changes:

  • Update the anniversaries query to avoid invalid DATE <> '' comparisons in strict mode.
  • Prevent Quill from re-initializing on parent re-renders by stabilizing the onChange handler via a ref and mounting Quill once.
  • Add Cypress API + UI regression tests covering both issues.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
src/ChurchCRM/SystemCalendars/AnniversariesCalendar.php Adjusts wedding date filtering to avoid MySQL 8 strict-mode crash.
react/components/QuillEditor.tsx Prevents repeated Quill initialization that caused toolbar duplication.
cypress/e2e/ui/events/standard.calendar.spec.js Adds UI regression test asserting toolbars don’t multiply after re-renders.
cypress/e2e/api/private/admin/private.admin.anniversaries.calendar.spec.js Adds API regression tests for the anniversaries system calendar endpoints.

Comment on lines 45 to 47
$families = FamilyQuery::create()
->filterByWeddingdate('', Criteria::NOT_EQUAL)
->filterByWeddingdate(null, Criteria::ISNOTNULL)
->find();
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filterByWeddingdate(null, Criteria::ISNOTNULL) will include any non-null sentinel/invalid DATE values (e.g. legacy 0000-00-00), which can lead to malformed event start dates when concatenating month/day later. Consider filtering for a valid minimum date (e.g. >= 0001-01-01) rather than only non-null, so you exclude both NULL and zero/empty legacy dates while still avoiding the strict-mode empty-string comparison.

Copilot uses AI. Check for mistakes.
Comment on lines 54 to 56
$families = FamilyQuery::create()
->filterByWeddingdate('', Criteria::NOT_EQUAL)
->filterByWeddingdate(null, Criteria::ISNOTNULL)
->filterById($Id)
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above in getEventById(): using only IS NOT NULL can allow legacy zero/empty wedding dates through, potentially generating invalid event start values. Consider constraining the range to valid dates (e.g. min date) instead of (or in addition to) the null check.

Copilot uses AI. Check for mistakes.
DawoudIO added 4 commits March 2, 2026 19:59
- MySQL 8.0 strict mode: DATE comparisons with empty strings
- React useEffect: stale closures and dependency array patterns
- Quill editor re-initialization bug from parent re-renders

Related to: #8128
…ndling

- Add missing beforeEach with cy.setupAdminSession()
- Handle empty datasets gracefully instead of failing
- Add logging via cy.log() for debugging
- Skip validations when no data exists, test still passes to confirm API is accessible

This fixes test unreliability caused by:
1. Missing session setup (beforeEach hook)
2. Tests failing when seed data lacks anniversary records
3. Asserting data existence instead of API correctness
- MenuEventsCount.php: Fixed critical invalid date range '0001-00-00' to ISNOTNULL
- people-families.php: Changed NOT_EQUAL to ISNOTNULL for consistency
- EventsMenuItems.php: Changed NOT_EQUAL to ISNOTNULL for consistency
- Updated database-operations.md with DATE filtering patterns
- Updated CLAUDE.md with test review workflow
- Updated SKILL.md with testing references
Copy link
Contributor

@DawoudIO DawoudIO left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good fixes for two real bugs. The PHP changes are correct and the overall approach is sound, but there are a few issues to address before merging.

CI Failures (must fix)

"Typecheck & Lint" and "Apply format fixes" both fail due to Biome lint errors in react/components/QuillEditor.tsx.

The project uses Biome, not ESLint, for the useExhaustiveDependencies rule. The comment added:

// eslint-disable-next-line react-hooks/exhaustive-deps

...only suppresses ESLint — Biome still flags it. Biome requires:

// biome-ignore lint/correctness/useExhaustiveDependencies: <reason>

Biome is specifically calling out three missing deps in the [] effect: name, placeholder, and value.

QuillEditor.tsx — Two issues

1. Wrong lint-suppression comment (blocks CI)

Replace the eslint comment with:

// biome-ignore lint/correctness/useExhaustiveDependencies: Quill is initialized once on mount; re-running the effect would append duplicate toolbars. name and placeholder are treated as mount-time constants; onChange is handled via onChangeRef; value is synced by a separate effect.

2. value prop changes after mount are silently ignored (potential regression)

The initialization effect uses value only at mount time. If the parent later updates value (e.g., loading an existing event for editing), the Quill editor won't reflect it. Add a separate sync effect:

// Sync value changes after initial mount without re-initializing Quill
useEffect(() => {
  if (quillRef.current && quillRef.current.root.innerHTML !== (value ?? "")) {
    quillRef.current.root.innerHTML = value ?? "";
  }
}, [value]);

This is the correct pattern when you need mount-once initialization but still want controlled updates.

PHP — Minor semantic change in MenuEventsCount.php

// Before
->filterByWeddingDate(['min' => '0001-00-00'])

// After
->filterByWeddingDate(null, Criteria::ISNOTNULL)

The original filtered for dates ≥ 0001-00-00, which excluded zero/null dates. ISNOTNULL will now include 0000-00-00 dates (zero dates stored by older MySQL versions without strict mode). This is unlikely to matter in practice since MySQL 8.0 strict mode prevents inserting zero dates — but worth noting. If there are legacy records with 0000-00-00, they'll now appear as anniversaries.

The other three ISNOTNULL changes (in AnniversariesCalendar.php, EventsMenuItems.php, people-families.php) are all correct.

Cypress Tests — Minor

The new UI test:

cy.get(".fc-daygrid-day").first().click();
cy.get(".modal-header input").should("be.visible");
cy.get(".ql-toolbar").should("have.length", 2);

There's no explicit wait for the modal to fully open before the first .ql-toolbar count assertion. If Quill initializes async, the count might briefly be 0 or 1. Consider using .should("have.length.gte", 1) first, or waiting for the modal container to be visible before asserting the exact count of 2.

Summary

Area Status
PHP MySQL 8.0 fix ✅ Correct
QuillEditor — wrong lint suppression ❌ Blocks CI
QuillEditor — value sync regression ⚠️ Missing
Cypress tests ✅ Good (minor fragility)
Agent skill docs ✅ Good

@DawoudIO
Copy link
Contributor

DawoudIO commented Mar 3, 2026

@copilot The CI failures in this PR are caused by a single issue in react/components/QuillEditor.tsx: the project uses Biome for linting, not ESLint, so the // eslint-disable-next-line comment is silently ignored and Biome still reports three useExhaustiveDependencies violations.

Please make these two changes to fix CI and close the remaining concern from the review:

1. Replace the ESLint comment with a Biome suppression comment

// biome-ignore lint/correctness/useExhaustiveDependencies: Quill is initialized once on mount; re-running would append duplicate toolbars. name/placeholder are mount-time constants; onChange is handled via onChangeRef; value is synced by a separate effect below.
useEffect(() => {

2. Add a separate effect to sync value prop changes after mount

Without this, editing an existing event (where value is pre-populated by the parent) will show an empty editor, because value is only read during initialization.

// Sync controlled value changes without re-initializing Quill
useEffect(() => {
  if (quillRef.current && quillRef.current.root.innerHTML !== (value ?? "")) {
    quillRef.current.root.innerHTML = value ?? "";
  }
}, [value]);

Place this after the onChangeRef sync effect and before the main initialization effect.

The correct patterns for this project are now documented in .agents/skills/churchcrm/webpack-typescript.md → "Biome Lint — Suppression Comments" section.

…ue sync effect

- Replace eslint-disable-next-line (ignored by Biome) with biome-ignore
  lint/correctness/useExhaustiveDependencies so CI lint passes
- Add separate useEffect([value]) to sync controlled value prop changes
  into Quill after mount, preventing empty editor when editing existing
  events with pre-populated content
- Document Biome lint suppression patterns in webpack-typescript.md skill

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Contributor Author

Copilot AI commented Mar 3, 2026

Good fixes for two real bugs. The PHP changes are correct and the overall approach is sound, but there are a few issues to address before merging.

CI Failures (must fix)...

All items addressed in commit daa9181:

  1. CI fix (lint) — replaced eslint-disable-next-line react-hooks/exhaustive-deps with biome-ignore lint/correctness/useExhaustiveDependencies: <reason>. The CI — Typecheck, Lint & Format workflow is now ✅ passing.

  2. value sync regression — added a separate useEffect([value]) that updates the Quill editor when the value prop changes after mount (prevents empty editor when loading an existing event for editing):

    useEffect(() => {
      if (quillRef.current && quillRef.current.root.innerHTML !== (value ?? "")) {
        quillRef.current.root.innerHTML = value ?? "";
      }
    }, [value]);
  3. Cypress modal wait — added .should("have.length.gte", 1) guard before the exact have.length, 2 assertion so Cypress waits for Quill to finish initializing before checking the count.

  4. PHP MenuEventsCount.php — the ISNOTNULL change is intentional and correct: ['min' => '0001-00-00'] uses an invalid date (month 0) that would also fail MySQL 8.0 strict mode. ISNOTNULL is the safe replacement, and 0000-00-00 legacy dates cannot be inserted under MySQL 8.0 strict mode anyway.

…points

renderJSON() has an `array $obj` type hint; passing a Propel ObjectCollection
directly causes a TypeError (HTTP 500) in PHP 8.4.

Apply ->toArray() on the returned ObjectCollection for both:
- GET /api/systemcalendars/{id}/events
- GET /api/systemcalendars/{id}/events/{eventid}

Fixes the failing Cypress test: "Returns 200 with anniversary events array".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Contributor Author

Copilot AI commented Mar 3, 2026

@copilot The CI failures in this PR are caused by a single issue in react/components/QuillEditor.tsx: the project uses Biome for linting, not ESLint, so the // eslint-disable-next-line comment...

Both changes were already applied in commit daa9181:

  1. biome-ignore comment — the eslint-disable-next-line was replaced with biome-ignore lint/correctness/useExhaustiveDependencies: initialized once on mount; re-running appends duplicate toolbars. onChange handled via onChangeRef; value synced by separate effect above; name/placeholder are mount-time constants.

  2. value sync effect — added before the init effect:

    // Sync value prop changes into Quill without re-initializing.
    useEffect(() => {
      if (quillRef.current && quillRef.current.root.innerHTML !== (value ?? "")) {
        quillRef.current.root.innerHTML = value ?? "";
      }
    }, [value]);

The CI — Typecheck, Lint & Format workflow is ✅ passing on that commit.

@DawoudIO DawoudIO merged commit e24f459 into master Mar 3, 2026
14 checks passed
@DawoudIO DawoudIO deleted the copilot/fix-calendar-issue-churchcrm branch March 3, 2026 19:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Churchcrm 7.0.0 calendar issue

3 participants