Skip to content

feat: support multiple syllabuses per course offering#337

Open
rileychh wants to merge 5 commits into
mainfrom
add-multiple-syllabuses
Open

feat: support multiple syllabuses per course offering#337
rileychh wants to merge 5 commits into
mainfrom
add-multiple-syllabuses

Conversation

@rileychh

@rileychh rileychh commented Jun 17, 2026

Copy link
Copy Markdown
Member

What

A course offering can have multiple syllabi — one per teacher who submits one. A syllabus's code is the authoring teacher's code, so each syllabus maps to a teacher.

Changes

  • New Syllabuses table holding per-teacher syllabus content (one row per offering + teacher); it replaces the offering's flat syllabus columns, while the shared header fields (course type, enrolled, withdrawn) stay on CourseOfferings.
  • Syllabi are fetched lazily per teacher via a watchSyllabus/refreshSyllabus pair keyed by (offeringId, teacherId): a row is created on the first successful fetch and deleted when the teacher hasn't submitted one; the refresh also writes the offering header fields and the instructor email.
  • getSyllabus returns null when the teacher hasn't submitted a syllabus (the page shows 尚未登錄, distinct from the session-expiry 尚未登錄入口網站).
  • The course offering detail exposes its teachers; the UI fetches each teacher's syllabus on demand (shows the first for now).

Testing

  • flutter analyze clean; generated Drift code regenerated.
  • Course service integration tests pass against real NTUT; verified a team-taught offering (10 teachers) and the 尚未登錄 (no-syllabus) path.

Temporary syllabus UI (debug)

Temporary debug UI in course_table_detail_sheet.dart listing the offering's teachers, each expandable to lazily fetch and dump its syllabus fields (or 查無大綱 when the teacher hasn't submitted one). Marked // TODO: temporary. Patch:

course_table_detail_sheet.dart.patch
diff --git a/lib/screens/main/course_table/course_table_detail_sheet.dart b/lib/screens/main/course_table/course_table_detail_sheet.dart
index fd0fe91..ded7e5e 100644
--- a/lib/screens/main/course_table/course_table_detail_sheet.dart
+++ b/lib/screens/main/course_table/course_table_detail_sheet.dart
@@ -1,6 +1,10 @@
 import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:tattoo/database/database.dart';
 import 'package:tattoo/i18n/strings.g.dart';
 import 'package:tattoo/repositories/course_repository.dart';
+import 'package:tattoo/screens/main/course_table/course_table_providers.dart';
+import 'package:tattoo/utils/localized.dart';
 
 Future<void> showCourseTableDetailSheet(
   BuildContext context, {
@@ -19,7 +23,7 @@ Future<void> showCourseTableDetailSheet(
   );
 }
 
-class CourseTableDetailSheet extends StatelessWidget {
+class CourseTableDetailSheet extends ConsumerWidget {
   const CourseTableDetailSheet({
     super.key,
     required this.cell,
@@ -28,12 +32,12 @@ class CourseTableDetailSheet extends StatelessWidget {
   final CourseTableCellData cell;
 
   @override
-  Widget build(BuildContext context) {
+  Widget build(BuildContext context, WidgetRef ref) {
     final theme = Theme.of(context);
 
     return SafeArea(
       top: false,
-      child: Padding(
+      child: SingleChildScrollView(
         padding: .fromLTRB(16, 8, 16, 16),
         child: Column(
           mainAxisSize: .min,
@@ -81,6 +85,8 @@ class CourseTableDetailSheet extends StatelessWidget {
                 ),
               ),
             ),
+            // TODO: temporary — replace with proper syllabus UI.
+            _SyllabiSection(offeringId: cell.id),
             SizedBox(height: MediaQuery.viewInsetsOf(context).bottom),
             // TODO: scroll up to show more course query features like classmate, course outline, etc.
           ],
@@ -89,3 +95,123 @@ class CourseTableDetailSheet extends StatelessWidget {
     );
   }
 }
+
+/// Temporary debug UI: lists the offering's teachers, each expandable to fetch
+/// and show that teacher's syllabus via [syllabusProvider].
+class _SyllabiSection extends ConsumerWidget {
+  const _SyllabiSection({required this.offeringId});
+
+  final int offeringId;
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final detail = ref.watch(courseOfferingProvider(offeringId));
+
+    return switch (detail) {
+      AsyncData(:final value?) when value.teachers.isNotEmpty => Column(
+        crossAxisAlignment: .start,
+        spacing: 8,
+        children: [
+          Text(
+            '課程大綱 (${value.teachers.length})',
+            style: Theme.of(context).textTheme.titleMedium,
+          ),
+          for (final teacher in value.teachers)
+            _SyllabusTile(offeringId: offeringId, teacher: teacher),
+        ],
+      ),
+      _ => const SizedBox.shrink(),
+    };
+  }
+}
+
+class _SyllabusTile extends ConsumerWidget {
+  const _SyllabusTile({required this.offeringId, required this.teacher});
+
+  final int offeringId;
+  final ({String code, String nameZh, String? nameEn}) teacher;
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final theme = Theme.of(context);
+    final teacherName = localized(teacher.nameZh, teacher.nameEn);
+
+    return Card(
+      margin: .zero,
+      elevation: 0,
+      shape: RoundedRectangleBorder(
+        borderRadius: .circular(12),
+        side: BorderSide(color: theme.colorScheme.outlineVariant),
+      ),
+      color: theme.colorScheme.surfaceContainer,
+      clipBehavior: .antiAlias,
+      child: ExpansionTile(
+        title: Text(teacherName),
+        subtitle: Text(teacher.code),
+        childrenPadding: .fromLTRB(16, 0, 16, 16),
+        expandedCrossAxisAlignment: .start,
+        children: [
+          _SyllabusContent(offeringId: offeringId, teacherId: teacher.code),
+        ],
+      ),
+    );
+  }
+}
+
+class _SyllabusContent extends ConsumerWidget {
+  const _SyllabusContent({required this.offeringId, required this.teacherId});
+
+  final int offeringId;
+  final String teacherId;
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final syllabus = ref.watch(
+      syllabusProvider((offeringId: offeringId, teacherId: teacherId)),
+    );
+
+    return switch (syllabus) {
+      AsyncData(:final value?) => _SyllabusFields(syllabus: value),
+      AsyncData() => const Text('查無大綱'),
+      AsyncError(:final error) => Text('載入失敗: $error'),
+      _ => const Padding(
+        padding: .all(8),
+        child: Center(child: CircularProgressIndicator()),
+      ),
+    };
+  }
+}
+
+class _SyllabusFields extends StatelessWidget {
+  const _SyllabusFields({required this.syllabus});
+
+  final Syllabus syllabus;
+
+  @override
+  Widget build(BuildContext context) {
+    final fields = <(String, String?)>[
+      ('最後更新', syllabus.updatedAt?.toString()),
+      ('課程大綱', syllabus.objective),
+      ('課程進度', syllabus.weeklyPlan),
+      ('評量方式', syllabus.evaluation),
+      ('使用教材', syllabus.textbooks),
+      ('備註', syllabus.remarks),
+    ];
+
+    return Column(
+      crossAxisAlignment: .start,
+      spacing: 8,
+      children: [
+        for (final (label, value) in fields)
+          if (value != null && value.isNotEmpty)
+            Column(
+              crossAxisAlignment: .start,
+              children: [
+                Text(label, style: Theme.of(context).textTheme.labelLarge),
+                Text(value),
+              ],
+            ),
+      ],
+    );
+  }
+}

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

PR Preview Builds

Build Number: 1150
Commit: bdcb494
Message: fix: emit refreshed syllabus after blocking fetch, not stale null

@rileychh rileychh force-pushed the add-multiple-syllabuses branch 2 times, most recently from 3c99c67 to 315392e Compare June 17, 2026 10:35
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 574a726e-2c02-4caa-8f08-a82793bcef73

📥 Commits

Reviewing files that changed from the base of the PR and between 53c9a20 and bdcb494.

📒 Files selected for processing (11)
  • lib/database/actions.dart
  • lib/database/database.dart
  • lib/database/database.g.dart
  • lib/database/schema.dart
  • lib/database/views.dart
  • lib/repositories/course_repository.dart
  • lib/screens/main/course_table/course_table_providers.dart
  • lib/services/course/course_service.dart
  • lib/services/course/mock_course_service.dart
  • lib/services/course/ntut_course_service.dart
  • test/services/course_service_test.dart
💤 Files with no reviewable changes (2)
  • lib/database/actions.dart
  • lib/database/views.dart
🚧 Files skipped from review as they are similar to previous changes (8)
  • test/services/course_service_test.dart
  • lib/database/database.dart
  • lib/screens/main/course_table/course_table_providers.dart
  • lib/database/schema.dart
  • lib/services/course/course_service.dart
  • lib/services/course/mock_course_service.dart
  • lib/repositories/course_repository.dart
  • lib/services/course/ntut_course_service.dart

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added separate syllabus loading for each course offering and teacher, so syllabus details can appear independently of the main course list.
    • Course offerings now show up-to-date enrollment and withdrawal counts directly from the database.
  • Bug Fixes

    • Improved handling when a syllabus has not yet been submitted, preventing missing-content errors.
    • Course data refreshes now use the latest available class information and no longer depend on outdated syllabus linkage.

Walkthrough

The PR moves syllabus content into a separate Syllabuses table, changes course-table parsing to support multiple syllabus IDs, and adds repository and Riverpod paths for nullable, per-teacher syllabus loading.

Changes

Syllabus Extraction and Multi-ID Refactor

Layer / File(s) Summary
Service contract and parsing
lib/services/course/course_service.dart, lib/services/course/ntut_course_service.dart, lib/services/course/mock_course_service.dart, test/services/course_service_test.dart
ScheduleDto uses syllabusIds: List<String>?, getSyllabus returns Future<SyllabusDto?>, and NTUT parsing now collects multiple syllabus IDs and returns null for “尚未登錄”. Mock fixtures and service tests are updated for the nullable syllabus flow.
Database schema and views
lib/database/schema.dart, lib/database/database.dart, lib/database/views.dart
CourseOfferings drops syllabus fields, Syllabuses is added with foreign keys and a uniqueness constraint, AppDatabase registers it, and CourseOfferingOverviews now projects enrollment and fetch timestamps.
Course offering upsert cleanup
lib/database/actions.dart
upsertCourseOffering removes syllabusId from its signature and from both insert and update companions.
Repository syllabus flow
lib/repositories/course_repository.dart
refreshCourseTable stops storing offering-level syllabus IDs, watchCourseOffering becomes DB-only, and new watchSyllabus/refreshSyllabus methods load and persist per-teacher syllabus data.
Riverpod syllabus provider
lib/screens/main/course_table/course_table_providers.dart
courseOfferingProvider docs are updated and a new syllabusProvider streams syllabus data by offering and teacher.

Sequence Diagram(s)

sequenceDiagram
  participant UI
  participant CourseRepository
  participant AppDatabase
  participant CourseService
  UI->>CourseRepository: watchSyllabus(offeringId, teacherId)
  CourseRepository->>AppDatabase: stream Syllabuses row
  alt syllabus missing or stale
    CourseRepository->>CourseService: getSyllabus(courseNumber, syllabusId)
    CourseService-->>CourseRepository: SyllabusDto?
    alt remote syllabus is null
      CourseRepository->>AppDatabase: delete Syllabuses row
    else remote syllabus exists
      CourseRepository->>AppDatabase: upsert Syllabuses row
      CourseRepository->>AppDatabase: update CourseOfferings fields
      CourseRepository->>AppDatabase: update TeacherSemesters email
    end
  end
  AppDatabase-->>CourseRepository: Syllabus?
  CourseRepository-->>UI: Stream<Syllabus?>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • NTUT-NPC/tattoo#336: Updates the course-offering teacher shape and persistence used by the new per-teacher syllabus lookup and storage path.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: supporting multiple syllabuses per course offering.
Description check ✅ Passed The description matches the changeset and accurately describes the new per-teacher syllabus flow and related UI updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-multiple-syllabuses

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
test/services/course_service_test.dart (1)

395-398: ⚡ Quick win

Exercise non-first syllabus IDs in integration tests.

These assertions always use the first ID, so regressions affecting secondary IDs in multi-syllabus offerings can pass undetected. Randomizing (or iterating) the selected ID improves coverage for this PR’s core behavior.

♻️ Proposed update
-        final syllabus = await courseService.getSyllabus(
-          courseNumber: course.number!,
-          syllabusId: course.syllabusIds!.first,
-        );
+        final syllabus = await courseService.getSyllabus(
+          courseNumber: course.number!,
+          syllabusId: course.syllabusIds!.pickRandom(),
+        );

Also applies to: 453-456, 503-506

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/services/course_service_test.dart` around lines 395 - 398, The test
always uses the first syllabus ID from course.syllabusIds!.first when calling
getSyllabus, which means secondary syllabus IDs are never tested and regressions
affecting them can pass undetected. Instead of always selecting the first ID,
randomize or iterate through different indices of the course.syllabusIds list to
exercise multiple syllabus IDs in the assertions. Apply this change to all three
occurrences of the getSyllabus call in the test file (around lines 395-398,
453-456, and 503-506).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/repositories/course_repository.dart`:
- Around line 757-767: After the refreshSyllabus(id) call succeeds, the code
currently yields the old syllabus stub instead of the refreshed data. To fix
this, fetch the updated syllabus from the repository after the successful
refreshSyllabus(id) call within the try block, and then yield that refreshed
syllabus. Only yield the original syllabus stub in the catch block to maintain
the current error-handling behavior. This ensures the UI receives the latest
data after a successful refresh rather than the stale pre-refresh version.

---

Nitpick comments:
In `@test/services/course_service_test.dart`:
- Around line 395-398: The test always uses the first syllabus ID from
course.syllabusIds!.first when calling getSyllabus, which means secondary
syllabus IDs are never tested and regressions affecting them can pass
undetected. Instead of always selecting the first ID, randomize or iterate
through different indices of the course.syllabusIds list to exercise multiple
syllabus IDs in the assertions. Apply this change to all three occurrences of
the getSyllabus call in the test file (around lines 395-398, 453-456, and
503-506).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1327bad3-f0f1-413a-9464-6887e881b801

📥 Commits

Reviewing files that changed from the base of the PR and between a10c34b and 315392e.

📒 Files selected for processing (11)
  • lib/database/actions.dart
  • lib/database/database.dart
  • lib/database/database.g.dart
  • lib/database/schema.dart
  • lib/database/views.dart
  • lib/repositories/course_repository.dart
  • lib/screens/main/course_table/course_table_providers.dart
  • lib/services/course/course_service.dart
  • lib/services/course/mock_course_service.dart
  • lib/services/course/ntut_course_service.dart
  • test/services/course_service_test.dart
💤 Files with no reviewable changes (2)
  • lib/database/views.dart
  • lib/database/actions.dart

Comment thread lib/repositories/course_repository.dart Outdated
@rileychh rileychh force-pushed the add-multiple-syllabuses branch from 315392e to 57ee454 Compare June 17, 2026 12:28

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
lib/database/schema.dart (1)

499-545: 💤 Low value

Consider adding an index on courseOffering for query consistency and performance.

Other tables with a courseOffering foreign key (Schedules, Materials) declare a @TableIndex on that column. Adding one here would maintain consistency and improve lookup performance when fetching syllabi for an offering.

♻️ Suggested change
 /// A teacher-authored syllabus for a course offering (教學大綱與進度).
 ...
 // Without `@DataClassName`, Drift names the row class 'Syllabuse'.
 `@DataClassName`('Syllabus')
+@TableIndex(name: 'syllabus_course_offering', columns: {`#courseOffering`})
 class Syllabuses extends Table with AutoIncrementId, Fetchable {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/database/schema.dart` around lines 499 - 545, The Syllabuses table is
missing a `@TableIndex` annotation on its courseOffering column, whereas other
tables with courseOffering foreign keys like Schedules and Materials declare
this index. Add a `@TableIndex` annotation on the courseOffering field in the
Syllabuses class to maintain consistency with other tables and improve query
performance when fetching syllabi for a specific course offering.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@lib/database/schema.dart`:
- Around line 499-545: The Syllabuses table is missing a `@TableIndex` annotation
on its courseOffering column, whereas other tables with courseOffering foreign
keys like Schedules and Materials declare this index. Add a `@TableIndex`
annotation on the courseOffering field in the Syllabuses class to maintain
consistency with other tables and improve query performance when fetching
syllabi for a specific course offering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 66c33263-1a70-4f29-a845-3411afaa33ce

📥 Commits

Reviewing files that changed from the base of the PR and between 315392e and 57ee454.

📒 Files selected for processing (11)
  • lib/database/actions.dart
  • lib/database/database.dart
  • lib/database/database.g.dart
  • lib/database/schema.dart
  • lib/database/views.dart
  • lib/repositories/course_repository.dart
  • lib/screens/main/course_table/course_table_providers.dart
  • lib/services/course/course_service.dart
  • lib/services/course/mock_course_service.dart
  • lib/services/course/ntut_course_service.dart
  • test/services/course_service_test.dart
💤 Files with no reviewable changes (2)
  • lib/database/views.dart
  • lib/database/actions.dart
🚧 Files skipped from review as they are similar to previous changes (7)
  • lib/database/database.dart
  • lib/services/course/course_service.dart
  • lib/screens/main/course_table/course_table_providers.dart
  • test/services/course_service_test.dart
  • lib/repositories/course_repository.dart
  • lib/services/course/mock_course_service.dart
  • lib/services/course/ntut_course_service.dart

@rileychh rileychh marked this pull request as ready for review June 17, 2026 14:06
@Dao-you Dao-you requested a review from Copilot June 17, 2026 15:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds support for multiple syllabi per course offering (one per teacher) by normalizing syllabus content into a new Syllabuses table and fetching syllabus content lazily, while keeping shared “header” fields on CourseOfferings. This fits the app’s existing pattern of caching parsed NTUT data in Drift and exposing reactive watchX() streams to the UI.

Changes:

  • Replace single ScheduleDto.syllabusId with syllabusIds and parse all 查詢 links from the course list.
  • Add Syllabuses table and repository flows to reconcile per-offering syllabus stubs and lazily watchSyllabus/refreshSyllabus.
  • Update course service integration tests and mock data to reflect multiple available syllabi.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/services/course_service_test.dart Adjusts integration tests to use syllabusIds and pick an available syllabus ID.
lib/services/course/course_service.dart Updates DTO contract and getSyllabus docs to describe teacher-code-based syllabus IDs.
lib/services/course/ntut_course_service.dart Parses multiple syllabus IDs from the 查詢 column and plumbs them into DTOs.
lib/services/course/mock_course_service.dart Updates mock course table data to provide syllabusIds lists.
lib/database/schema.dart Removes offering-level flat syllabus content columns; adds new Syllabuses table keyed by offering+teacher.
lib/database/database.dart Registers the new Syllabuses table with the Drift database.
lib/database/views.dart Removes syllabus content fields from CourseOfferingOverviews view selection.
lib/database/actions.dart Updates course offering upsert helper to no longer accept/store a single syllabusId.
lib/repositories/course_repository.dart Adds offering-level syllabi composition, stub reconciliation, and lazy watchSyllabus/refreshSyllabus.
lib/screens/main/course_table/course_table_providers.dart Adds syllabusProvider and updates offering provider docs to reflect DB-composed detail + lazy syllabus fetch.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/repositories/course_repository.dart Outdated
Comment thread lib/repositories/course_repository.dart Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/repositories/course_repository.dart`:
- Around line 696-705: After the `refreshSyllabus` call succeeds (within the try
block), re-fetch the syllabus value from the database before yielding it instead
of yielding the stale `null` value. This ensures that after a successful
refresh, the code queries the updated data from the database and emits that
fresh value, preventing the UI from seeing a stale null state before the
database stream re-emits.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d959898-ec95-49bf-9a3a-4ef1f7b70f9f

📥 Commits

Reviewing files that changed from the base of the PR and between 57ee454 and 497ec05.

📒 Files selected for processing (8)
  • lib/database/database.g.dart
  • lib/database/schema.dart
  • lib/repositories/course_repository.dart
  • lib/screens/main/course_table/course_table_providers.dart
  • lib/services/course/course_service.dart
  • lib/services/course/mock_course_service.dart
  • lib/services/course/ntut_course_service.dart
  • test/services/course_service_test.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/screens/main/course_table/course_table_providers.dart

Comment thread lib/repositories/course_repository.dart
Base automatically changed from add-multiple-teachers to main June 26, 2026 03:58
rileychh added 5 commits June 26, 2026 11:59
A course offering can have multiple syllabi — one per teacher who submits
one. Each 查詢 link in the course list carries the authoring teacher's code,
so a syllabus maps to a teacher (built on multiple-teacher support).

Add a Syllabuses table (one row per offering+teacher) holding the per-teacher
syllabus content, and move the shared header fields off the flat
CourseOfferings columns. refreshCourseTable records which syllabi are
available as stub rows; their content is fetched lazily and per-teacher via a
new watchSyllabus/refreshSyllabus pair (which also writes the offering-level
header fields and the instructor email). The course offering detail exposes
the available syllabi; the UI shows the first for now.
Pick a random syllabus id from syllabusIds instead of always the first,
so secondary syllabus ids are exercised and regressions affecting them
are caught.
The stub branch of watchSyllabus awaited refreshSyllabus then yielded
the empty stub row, flashing empty content until the DB write's re-emit
delivered fresh data — defeating the branch's stated purpose. Re-read
the row after a successful refresh and yield that; yield the stub only
on failure so the UI still exits its loading state.
A syllabus's code is its author's teacher code, so any teacher may have one.
Stop enumerating syllabusIds; key a syllabus by (offering, teacher) and fetch
it lazily per teacher.

- getSyllabus returns null on 尚未登錄 (red marker, distinct from the session
  尚未登錄入口網站).
- watch/refreshSyllabus take (offeringId, teacherId); rows are upserted on a
  successful fetch, deleted on 尚未登錄, no stubs pre-created.
- Drop OfferingSyllabus/CourseOfferingDetail.syllabi and unused
  Syllabuses.fetchedAt; the UI drives syllabi off the offering's teachers.
watchSyllabus re-queries after a successful first fetch so the UI gets the
inserted row instead of the stale null snapshot before the stream re-emits.
Also reword the doc — it shows the cached row first, so drop the
contradictory "rather than caching".
@rileychh rileychh force-pushed the add-multiple-syllabuses branch from 53c9a20 to bdcb494 Compare June 26, 2026 03:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants