SPB Git

spb/spboucher.ai Public

spboucher.ai — personal website of Simon-Pierre Boucher.

TypeScript 93.4% HTML 5.5% CSS 1%

Teaching: direct PDF links for every slide, course plan and TP

Each UQO course card now links directly to all 14 session slide PDFs
(numbered pill per séance, topic tooltip on IMM1033), the course plan,
and each TP énoncé by name — all via raw.githubusercontent.com so the
PDF opens directly in the browser. All 37 URLs verified 200.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 6 days ago (Aug 5, 2026) parent 5ce47dc

Showing 1 changed file with +117 and −24

modified app/teaching/page.tsx +117 −24
@@ -25,15 +25,60 @@ export const metadata: Metadata = {
25 25 };
26 26
27 27 const UQO_REPO = "https://github.com/spboucher-ai/UQO_COURS_PUBLIC";
28 +const UQO_RAW =
29 + "https://raw.githubusercontent.com/spboucher-ai/UQO_COURS_PUBLIC/main";
28 30
29 const uqoCourses = [
31 +const imm1033Slides = [
32 + "seance01_introduction",
33 + "seance02_cadre_conceptuel",
34 + "seance03_evaluation_terrain",
35 + "seance04_analyse_terrain",
36 + "seance05_reproduction_remplacement",
37 + "seance06_methodes_estimation",
38 + "seance07_couts_directs",
39 + "seance08_couts_indirects",
40 + "seance09_concepts_depreciation",
41 + "seance10_depreciation_physique",
42 + "seance11_depreciation_fonctionnelle",
43 + "seance12_depreciation_economique",
44 + "seance13_applications_specialisees",
45 + "seance14_synthese_revision",
46 +];
47 +
48 +interface UqoCourse {
49 + code: string;
50 + title: string;
51 + description: string;
52 + dir: string;
53 + slides: { label: string; file: string; topic?: string }[];
54 + tps: { label: string; file: string }[];
55 +}
56 +
57 +const uqoCourses: UqoCourse[] = [
30 58 {
31 59 code: "IMM1003",
32 60 title: "Éléments d'évaluation immobilière",
33 61 description:
34 62 "Foundations of real-estate appraisal — market analysis, comparison and income methods.",
35 63 dir: "IMM1003-20_Elements_evaluation_immobiliere",
36 slidesLabel: "Slides — 14 sessions (PDF)",
64 + slides: Array.from({ length: 14 }, (_, i) => {
65 + const n = String(i + 1).padStart(2, "0");
66 + return { label: n, file: `02_Slides/pdf/seance${n}.pdf` };
67 + }),
68 + tps: [
69 + {
70 + label: "TP1 — Tableau de bord du marché",
71 + file: "03_Travaux_pratiques/TP1_Tableau_de_bord_du_marche/enonce/IMM1003-20_TP1_enonce.pdf",
72 + },
73 + {
74 + label: "TP2 — Méthode de comparaison",
75 + file: "03_Travaux_pratiques/TP2_Methode_de_comparaison/enonce/IMM1003-20_TP2_enonce.pdf",
76 + },
77 + {
78 + label: "TP3 — Méthode du revenu",
79 + file: "03_Travaux_pratiques/TP3_Methode_du_revenu/enonce/IMM1003-20_TP3_enonce.pdf",
80 + },
81 + ],
37 82 },
38 83 {
39 84 code: "IMM1033",
@@ -41,7 +86,25 @@ const uqoCourses = [
41 86 description:
42 87 "Cost approach to valuation — land value, replacement cost, and depreciation analysis.",
43 88 dir: "IMM1033-20_Methodes_du_cout",
44 slidesLabel: "Slides — 14 sessions (PDF)",
89 + slides: imm1033Slides.map((name, i) => ({
90 + label: String(i + 1).padStart(2, "0"),
91 + file: `02_Slides/pdf/${name}.pdf`,
92 + topic: name.slice(9).replace(/_/g, " "),
93 + })),
94 + tps: [
95 + {
96 + label: "TP1 — Évaluation d'un terrain",
97 + file: "03_Travaux_pratiques/TP1_Evaluation_d_un_terrain/enonce/IMM1033-20_TP1_enonce.pdf",
98 + },
99 + {
100 + label: "TP2 — Bordereau de coût neuf",
101 + file: "03_Travaux_pratiques/TP2_Bordereau_de_cout_neuf/enonce/IMM1033-20_TP2_enonce.pdf",
102 + },
103 + {
104 + label: "TP3 — Ventilation de la dépréciation",
105 + file: "03_Travaux_pratiques/TP3_Ventilation_de_la_depreciation/enonce/IMM1033-20_TP3_enonce.pdf",
106 + },
107 + ],
45 108 },
46 109 ];
47 110
@@ -127,9 +190,9 @@ export default function TeachingPage() {
127 190 </CardTitle>
128 191 <CardDescription>{course.description}</CardDescription>
129 192 </CardHeader>
130 <CardContent className="flex flex-1 flex-col gap-2.5">
193 + <CardContent className="flex flex-1 flex-col gap-4">
131 194 <a
132 href={`${UQO_REPO}/blob/main/${course.dir}/01_Plan_de_cours/plan_de_cours.pdf`}
195 + href={`${UQO_RAW}/${course.dir}/01_Plan_de_cours/plan_de_cours.pdf`}
133 196 target="_blank"
134 197 rel="noopener noreferrer"
135 198 className="inline-flex items-center gap-2 text-sm font-medium text-primary hover:underline"
@@ -137,24 +200,54 @@ export default function TeachingPage() {
137 200 <FileText className="h-4 w-4" aria-hidden="true" />
138 201 Course plan (PDF)
139 202 </a>
140 <a
141 href={`${UQO_REPO}/tree/main/${course.dir}/02_Slides/pdf`}
142 target="_blank"
143 rel="noopener noreferrer"
144 className="inline-flex items-center gap-2 text-sm font-medium text-primary hover:underline"
145 >
146 <Presentation className="h-4 w-4" aria-hidden="true" />
147 {course.slidesLabel}
148 </a>
149 <a
150 href={`${UQO_REPO}/tree/main/${course.dir}/03_Travaux_pratiques`}
151 target="_blank"
152 rel="noopener noreferrer"
153 className="inline-flex items-center gap-2 text-sm font-medium text-primary hover:underline"
154 >
155 <Wrench className="h-4 w-4" aria-hidden="true" />
156 Practical assignments (3 TPs)
157 </a>
203 +
204 + <div>
205 + <p className="inline-flex items-center gap-2 text-sm font-medium">
206 + <Presentation
207 + className="h-4 w-4 text-primary"
208 + aria-hidden="true"
209 + />
210 + Slides — 14 sessions (PDF)
211 + </p>
212 + <div className="mt-2 flex flex-wrap gap-1.5">
213 + {course.slides.map((slide) => (
214 + <a
215 + key={slide.file}
216 + href={`${UQO_RAW}/${course.dir}/${slide.file}`}
217 + target="_blank"
218 + rel="noopener noreferrer"
219 + title={slide.topic}
220 + aria-label={`Session ${slide.label}${slide.topic ? ` — ${slide.topic}` : ""} (PDF)`}
221 + className="inline-flex h-8 w-9 items-center justify-center rounded-full border border-border/70 text-xs font-medium text-muted-foreground transition-colors hover:border-primary/40 hover:bg-accent hover:text-primary"
222 + >
223 + {slide.label}
224 + </a>
225 + ))}
226 + </div>
227 + </div>
228 +
229 + <div>
230 + <p className="inline-flex items-center gap-2 text-sm font-medium">
231 + <Wrench
232 + className="h-4 w-4 text-primary"
233 + aria-hidden="true"
234 + />
235 + Practical assignments (PDF)
236 + </p>
237 + <div className="mt-2 flex flex-col gap-1.5">
238 + {course.tps.map((tp) => (
239 + <a
240 + key={tp.file}
241 + href={`${UQO_RAW}/${course.dir}/${tp.file}`}
242 + target="_blank"
243 + rel="noopener noreferrer"
244 + className="text-sm text-primary hover:underline"
245 + >
246 + {tp.label}
247 + </a>
248 + ))}
249 + </div>
250 + </div>
158 251 </CardContent>
159 252 </Card>
160 253 </Reveal>
@@ -163,7 +256,7 @@ export default function TeachingPage() {
163 256 <Reveal className="mt-4">
164 257 <div className="flex flex-wrap gap-4">
165 258 <a
166 href={`${UQO_REPO}/blob/main/Rapport_Immobilier_Quebec/rapport_immobilier_quebec.pdf`}
259 + href={`${UQO_RAW}/Rapport_Immobilier_Quebec/rapport_immobilier_quebec.pdf`}
167 260 target="_blank"
168 261 rel="noopener noreferrer"
169 262 className="inline-flex items-center gap-2 text-sm font-medium text-primary hover:underline"
170 263