diff --git a/frontend/website/cypress/e2e/set-scheduling.cy.ts b/frontend/website/cypress/e2e/set-scheduling.cy.ts index 2a6984e..0511405 100644 --- a/frontend/website/cypress/e2e/set-scheduling.cy.ts +++ b/frontend/website/cypress/e2e/set-scheduling.cy.ts @@ -236,10 +236,8 @@ describe('set scheduling', () => { 'contain.text', 'Exodus / Shemot / Chapter 1', ) - cy.get('[data-assignment-section="completed"]') - .should('not.have.attr', 'open') - .find('summary') - .click() + cy.get('[data-assignment-section="completed"]').should('not.have.attr', 'open') + cy.get('[data-assignment-section="completed"] summary').click() cy.get('[data-assignment-section="completed"]').should( 'contain.text', 'No assignments completed yet.', @@ -342,26 +340,29 @@ describe('set scheduling', () => { cy.get('[data-schedule-date="2026-08-13"]').should('not.exist') }) - cy.get('[data-assignment-section="completed"]') - .should('have.attr', 'data-assignment-count', '2') - .and('not.have.attr', 'open') - .within(() => { - cy.get('summary').should('contain.text', 'Completed').click() - cy.get('[data-schedule-day]').then(($days) => { - expect([...$days].map((day) => day.getAttribute('data-schedule-date'))).to.deep.equal([ - '2026-08-12', - '2026-08-13', - ]) - }) - cy.get('[data-schedule-date="2026-08-12"]') - .should('contain.text', 'Exodus / Shemot / Chapter 1') - .and('not.contain.text', 'Leviticus / Vayikra / Chapter 1') - cy.get('[data-schedule-date="2026-08-13"]').should( - 'contain.text', - 'Numbers / Bamidbar / Chapter 1', - ) - cy.contains('Rest day').should('not.exist') + cy.get('[data-assignment-section="completed"]').should( + 'have.attr', + 'data-assignment-count', + '2', + ) + cy.get('[data-assignment-section="completed"]').should('not.have.attr', 'open') + cy.get('[data-assignment-section="completed"]').within(() => { + cy.get('summary').should('contain.text', 'Completed').click() + cy.get('[data-schedule-day]').then(($days) => { + expect([...$days].map((day) => day.getAttribute('data-schedule-date'))).to.deep.equal([ + '2026-08-12', + '2026-08-13', + ]) }) + cy.get('[data-schedule-date="2026-08-12"]') + .should('contain.text', 'Exodus / Shemot / Chapter 1') + .and('not.contain.text', 'Leviticus / Vayikra / Chapter 1') + cy.get('[data-schedule-date="2026-08-13"]').should( + 'contain.text', + 'Numbers / Bamidbar / Chapter 1', + ) + cy.contains('Rest day').should('not.exist') + }) cy.viewport(375, 667) cy.document().then((document) => { diff --git a/frontend/website/src/components/ScheduleAssignmentTimeline.vue b/frontend/website/src/components/ScheduleAssignmentTimeline.vue new file mode 100644 index 0000000..779d712 --- /dev/null +++ b/frontend/website/src/components/ScheduleAssignmentTimeline.vue @@ -0,0 +1,335 @@ + + + + + diff --git a/frontend/website/src/views/ScheduleDetailView.vue b/frontend/website/src/views/ScheduleDetailView.vue index 9a455a9..5c72243 100644 --- a/frontend/website/src/views/ScheduleDetailView.vue +++ b/frontend/website/src/views/ScheduleDetailView.vue @@ -1,15 +1,55 @@ @@ -93,73 +145,82 @@ async function setAssignmentCompleted(assignmentId: number, completed: boolean):

-
    -
  1. -
    - - {{ day.assignments.length }} assigned -
    +

    + {{ completionAnnouncement }} +

    -

    Rest day

    +
    +
    +
    +

    Your plan

    +

    Remaining

    +

    Everything still ahead, kept in scheduled order.

    +
    + + {{ remainingAssignmentCount }} + {{ remainingAssignmentCount === 1 ? 'assignment' : 'assignments' }} + +
    -
      -
    • -
      - - {{ assignment.element.path.join(' / ') }} - - {{ assignment.element.kind }} -
      -
      -

      - Completed - -

      -

      Not completed

      - - -
      -
    • -
    -
  2. -
+

+ All assignments are complete. +

+ + + + +
+ + + Completed + Finished work, grouped by its original schedule date. + + + + {{ completedAssignmentCount }} + {{ completedAssignmentCount === 1 ? 'assignment' : 'assignments' }} + + + + + +
+

+ No assignments completed yet. +

+ +
+
@@ -233,153 +294,142 @@ h1 { color: #68776f; } -.schedule-days { - display: grid; - gap: 1rem; - margin: 3rem 0 0; +.completion-announcement { + position: absolute; + width: 1px; + height: 1px; padding: 0; - list-style: none; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; } -.schedule-day { +.assignment-section { + margin-top: 3rem; +} + +.assignment-section__header { + display: flex; + align-items: end; + justify-content: space-between; + gap: 2rem; +} + +.assignment-section__eyebrow { + margin: 0 0 0.45rem; + color: #926044; + font-size: 0.68rem; + font-weight: 800; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.assignment-section__header h2, +.completed-section__heading { + font-family: Georgia, 'Times New Roman', serif; + font-weight: 500; + letter-spacing: -0.035em; +} + +.assignment-section__header h2 { + margin: 0; + font-size: clamp(2rem, 5vw, 3rem); + line-height: 1; +} + +.assignment-section__header p:last-child { + margin: 0.65rem 0 0; + color: #68776f; + line-height: 1.55; +} + +.assignment-section__count { + flex: 0 0 auto; + padding: 0.45rem 0.7rem; + border-radius: 999px; + color: #4f665d; + background: rgb(224 233 223 / 72%); + font-size: 0.72rem; + font-weight: 800; +} + +.section-state { + display: grid; + min-height: 6rem; + place-items: center; + margin: 1.4rem 0 0; + padding: 1.25rem; + border: 1px dashed rgb(24 48 41 / 18%); + border-radius: 1rem; + color: #5e7067; + background: rgb(255 253 247 / 52%); + text-align: center; +} + +.completed-section { overflow: hidden; border: 1px solid rgb(24 48 41 / 12%); border-radius: 1rem; - background: rgb(255 253 247 / 82%); - box-shadow: 0 0.75rem 2rem rgb(40 62 52 / 6%); + background: rgb(246 245 237 / 78%); } -.schedule-day__header { +.completed-section__summary { display: flex; align-items: center; justify-content: space-between; - gap: 1rem; - padding: 0.85rem 1rem; - border-bottom: 1px solid rgb(24 48 41 / 10%); - color: #5e7067; - background: rgb(239 228 212 / 42%); - font-size: 0.75rem; - font-weight: 800; -} - -.rest-day { - margin: 0; - padding: 1.2rem 1rem; - color: #79877f; - font-style: italic; -} - -.assignment-list { - display: grid; - gap: 0; - margin: 0; - padding: 0; + gap: 1.5rem; + padding: 1.15rem 1.25rem; + cursor: pointer; list-style: none; } -.assignment-list li { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - align-items: start; - gap: 1rem; - padding: 1rem; +.completed-section__summary::-webkit-details-marker { + display: none; } -.assignment-list li.is-completed { - background: rgb(220 235 224 / 34%); -} - -.assignment-list li + li { - border-top: 1px solid rgb(24 48 41 / 10%); -} - -.assignment-path { - min-width: 0; - font-family: Georgia, 'Times New Roman', serif; - font-size: 1.05rem; - overflow-wrap: anywhere; -} - -.assignment-description { - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; - min-width: 0; -} - -.assignment-kind { - flex: 0 0 auto; - padding: 0.35rem 0.55rem; - border-radius: 999px; - color: #81533a; - background: #efe4d4; - font-size: 0.62rem; - font-weight: 800; - letter-spacing: 0.1em; - text-transform: none; -} - -.assignment-completion { - display: grid; - justify-items: end; - gap: 0.55rem; - min-width: 12rem; -} - -.assignment-completion__status, -.assignment-completion__error { - margin: 0; - font-size: 0.74rem; - font-weight: 700; - text-align: right; -} - -.assignment-completion__status { - color: #5e7067; -} - -.assignment-completion__status time { - display: block; - margin-top: 0.2rem; - color: #344e45; -} - -.assignment-completion__error { - max-width: 14rem; - color: #9b3f32; -} - -.assignment-completion-button { - min-height: 2.4rem; - padding: 0.55rem 0.8rem; - border: 1px solid rgb(24 58 49 / 28%); - border-radius: 0.65rem; - color: #183a31; - background: #fffdf7; - font-size: 0.75rem; - font-weight: 800; - cursor: pointer; -} - -.is-completed .assignment-completion-button { - color: #5e7067; - background: transparent; -} - -.assignment-completion-button:hover:not(:disabled) { - border-color: #285c4e; - background: #f9f5e9; -} - -.assignment-completion-button:focus-visible { +.completed-section__summary:focus-visible { outline: 3px solid rgb(86 127 112 / 34%); - outline-offset: 0.25rem; + outline-offset: -0.25rem; } -.assignment-completion-button:disabled { - cursor: wait; - opacity: 0.65; +.completed-section__introduction { + display: grid; + gap: 0.25rem; +} + +.completed-section__heading { + color: #344e45; + font-size: 1.35rem; +} + +.completed-section__introduction > span:last-child { + color: #79877f; + font-size: 0.78rem; + line-height: 1.45; +} + +.completed-section__controls { + display: inline-flex; + align-items: center; + gap: 0.75rem; +} + +.completed-section__chevron { + color: #68776f; + font-size: 1.35rem; + line-height: 1; + transition: transform 160ms ease; +} + +.completed-section[open] .completed-section__chevron { + transform: rotate(180deg); +} + +.completed-section__content { + padding: 0 1rem 1rem; + border-top: 1px solid rgb(24 48 41 / 10%); } .page-state { @@ -430,22 +480,26 @@ h1 { margin-top: 3rem; } - .assignment-list li { - grid-template-columns: 1fr; - } - - .assignment-description { + .assignment-section__header { align-items: flex-start; + flex-direction: column; + gap: 1rem; } - .assignment-completion { - justify-items: start; - min-width: 0; + .completed-section__summary { + align-items: flex-start; + gap: 1rem; + padding: 1rem; } - .assignment-completion__status, - .assignment-completion__error { - text-align: left; + .completed-section__controls { + flex-direction: column; + align-items: flex-end; + gap: 0.35rem; + } + + .completed-section__content { + padding: 0 0.75rem 0.75rem; } }