feat(v3): restructure project + add complete frontend

- Restructure: move backend from new_project/ to backend/
- Add full React/TypeScript frontend (37 pages, 17 services, 16 type defs, 11 query hooks)
- Add docs/ with SRS specs, user stories, and workflow documentation
- Update .gitignore for new directory layout

Workflows implemented:
  WF1 User Signup, WF2 Placement Test, WF3 Exam Configuration,
  WF4 General English Exam, WF5 Course Generation,
  WF6 Entity Student Onboarding, AI Course Generation,
  Adaptive Learning Engine UI, White-Label Branding, Score Release

Made-with: Cursor
This commit is contained in:
Yamen Ahmad
2026-04-10 17:26:42 +04:00
commit 907a5c0e92
331 changed files with 23511 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- Top-level website menu for student portal -->
<record id="menu_encoach_portal_root" model="website.menu">
<field name="name">My Learning</field>
<field name="url">/my/dashboard</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence">50</field>
</record>
<record id="menu_encoach_dashboard" model="website.menu">
<field name="name">Dashboard</field>
<field name="url">/my/dashboard</field>
<field name="parent_id" ref="menu_encoach_portal_root"/>
<field name="sequence">10</field>
</record>
<record id="menu_encoach_placement" model="website.menu">
<field name="name">Placement Test</field>
<field name="url">/my/placement</field>
<field name="parent_id" ref="menu_encoach_portal_root"/>
<field name="sequence">20</field>
</record>
<record id="menu_encoach_profile" model="website.menu">
<field name="name">My Profile</field>
<field name="url">/my/profile</field>
<field name="parent_id" ref="menu_encoach_portal_root"/>
<field name="sequence">30</field>
</record>
</odoo>

View File

@@ -0,0 +1,858 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- ===================================================================
REGISTER PAGE
=================================================================== -->
<template id="register" name="EnCoach Register">
<t t-call="website.layout">
<div id="wrap" class="oe_structure">
<section class="ec-register-section py-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-5 col-md-7">
<div class="card shadow-sm border-0 rounded-4">
<div class="card-body p-4 p-md-5">
<div class="text-center mb-4">
<h2 class="fw-bold text-primary mb-1">Create Account</h2>
<p class="text-muted">Start your learning journey with EnCoach</p>
</div>
<form action="/register" method="post" class="ec-register-form">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="mb-3">
<label for="reg_name" class="form-label fw-semibold">Full Name</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="fa fa-user text-muted"/></span>
<input type="text" id="reg_name" name="name" class="form-control border-start-0" placeholder="Enter your full name" required="required"/>
</div>
</div>
<div class="mb-3">
<label for="reg_email" class="form-label fw-semibold">Email Address</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="fa fa-envelope text-muted"/></span>
<input type="email" id="reg_email" name="email" class="form-control border-start-0" placeholder="you@example.com" required="required"/>
</div>
</div>
<div class="mb-3">
<label for="reg_password" class="form-label fw-semibold">Password</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="fa fa-lock text-muted"/></span>
<input type="password" id="reg_password" name="password" class="form-control border-start-0" placeholder="Min. 8 characters" required="required" minlength="8"/>
</div>
<div class="ec-password-strength mt-2">
<div class="progress" style="height: 4px;">
<div id="password_strength_bar" class="progress-bar" role="progressbar" style="width: 0%;"/>
</div>
<small id="password_strength_text" class="text-muted"/>
</div>
</div>
<div class="mb-4">
<label for="reg_confirm" class="form-label fw-semibold">Confirm Password</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="fa fa-lock text-muted"/></span>
<input type="password" id="reg_confirm" name="confirm_password" class="form-control border-start-0" placeholder="Re-enter password" required="required"/>
</div>
</div>
<t t-if="captcha_site_key">
<div class="mb-4 d-flex justify-content-center">
<div class="ec-captcha-placeholder border rounded-3 p-3 text-center bg-light w-100">
<small class="text-muted"><i class="fa fa-shield me-1"/>CAPTCHA verification</small>
<div t-att-data-sitekey="captcha_site_key" class="g-recaptcha mt-2" data-callback="onCaptchaSuccess"/>
</div>
</div>
</t>
<button type="submit" class="btn btn-primary w-100 py-2 fw-semibold rounded-3">
<i class="fa fa-user-plus me-2"/>Create Account
</button>
</form>
<div class="text-center mt-4">
<span class="text-muted">Already have an account?</span>
<a href="/web/login" class="fw-semibold text-decoration-none ms-1">Sign In</a>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</t>
</template>
<!-- ===================================================================
VERIFY EMAIL PAGE
=================================================================== -->
<template id="verify_email" name="EnCoach Verify Email">
<t t-call="website.layout">
<div id="wrap" class="oe_structure">
<section class="ec-verify-section py-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-5 col-md-7">
<div class="card shadow-sm border-0 rounded-4">
<div class="card-body p-4 p-md-5 text-center">
<div class="ec-verify-icon mb-4">
<span class="d-inline-flex align-items-center justify-content-center rounded-circle bg-primary bg-opacity-10" style="width:72px;height:72px;">
<i class="fa fa-envelope-open fa-2x text-primary"/>
</span>
</div>
<h2 class="fw-bold mb-2">Check Your Email</h2>
<p class="text-muted mb-1">We sent a verification code to</p>
<p class="fw-semibold mb-4" t-esc="email"/>
<form action="/verify-email" method="post">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<input type="hidden" name="email" t-att-value="email"/>
<div class="ec-otp-inputs d-flex justify-content-center gap-2 mb-4">
<input type="text" name="otp_1" maxlength="1" class="form-control text-center fw-bold fs-4 rounded-3 ec-otp-box" required="required"/>
<input type="text" name="otp_2" maxlength="1" class="form-control text-center fw-bold fs-4 rounded-3 ec-otp-box" required="required"/>
<input type="text" name="otp_3" maxlength="1" class="form-control text-center fw-bold fs-4 rounded-3 ec-otp-box" required="required"/>
<input type="text" name="otp_4" maxlength="1" class="form-control text-center fw-bold fs-4 rounded-3 ec-otp-box" required="required"/>
<input type="text" name="otp_5" maxlength="1" class="form-control text-center fw-bold fs-4 rounded-3 ec-otp-box" required="required"/>
<input type="text" name="otp_6" maxlength="1" class="form-control text-center fw-bold fs-4 rounded-3 ec-otp-box" required="required"/>
</div>
<button type="submit" class="btn btn-primary w-100 py-2 fw-semibold rounded-3 mb-3">
Verify Email
</button>
</form>
<p class="text-muted mb-0">
Didn't receive the code?
<a href="#" class="fw-semibold text-decoration-none ec-resend-btn">Resend</a>
</p>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</t>
</template>
<!-- ===================================================================
ONBOARDING PAGE (OWL Wizard mount point)
=================================================================== -->
<template id="onboarding" name="EnCoach Onboarding">
<t t-call="website.layout">
<div id="wrap" class="oe_structure">
<section class="ec-onboarding-section py-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8 col-md-10">
<div class="text-center mb-4">
<h2 class="fw-bold">Welcome, <t t-esc="user.name"/>!</h2>
<p class="text-muted">Let's personalise your learning experience</p>
</div>
<!-- OWL wizard mounts here -->
<div id="ec_onboarding_wizard"
t-att-data-user-id="user.id"
t-att-data-profile-id="profile.id if profile else ''"/>
</div>
</div>
</div>
</section>
</div>
</t>
</template>
<!-- ===================================================================
STUDENT DASHBOARD
=================================================================== -->
<template id="student_dashboard" name="EnCoach Student Dashboard">
<t t-call="website.layout">
<div id="wrap" class="oe_structure">
<section class="ec-dashboard-section py-5">
<div class="container">
<!-- Welcome Header -->
<div class="row align-items-center mb-5">
<div class="col">
<h2 class="fw-bold mb-1">Welcome back, <t t-esc="user.name"/>!</h2>
<p class="text-muted mb-0">
<t t-if="profile and profile.cefr_level">
Your current level:
<span class="badge rounded-pill bg-primary fs-6 ms-1" t-esc="profile.cefr_level"/>
</t>
<t t-else="">
<span class="text-warning"><i class="fa fa-exclamation-circle me-1"/>Take a placement test to find your level</span>
</t>
</p>
</div>
<div class="col-auto">
<a href="/my/placement" class="btn btn-outline-primary rounded-3">
<i class="fa fa-compass me-1"/>Placement Test
</a>
</div>
</div>
<!-- Quick Actions -->
<div class="row g-3 mb-5">
<div class="col-md-4">
<a href="/my/placement" class="card border-0 shadow-sm rounded-4 h-100 text-decoration-none ec-action-card">
<div class="card-body d-flex align-items-center p-4">
<span class="d-inline-flex align-items-center justify-content-center rounded-3 bg-primary bg-opacity-10 me-3" style="width:48px;height:48px;">
<i class="fa fa-play text-primary"/>
</span>
<div>
<h6 class="fw-bold mb-0">Start Placement</h6>
<small class="text-muted">Find your level</small>
</div>
</div>
</a>
</div>
<div class="col-md-4">
<a href="#courses_section" class="card border-0 shadow-sm rounded-4 h-100 text-decoration-none ec-action-card">
<div class="card-body d-flex align-items-center p-4">
<span class="d-inline-flex align-items-center justify-content-center rounded-3 bg-success bg-opacity-10 me-3" style="width:48px;height:48px;">
<i class="fa fa-book text-success"/>
</span>
<div>
<h6 class="fw-bold mb-0">Resume Course</h6>
<small class="text-muted">Continue learning</small>
</div>
</div>
</a>
</div>
<div class="col-md-4">
<a href="#results_section" class="card border-0 shadow-sm rounded-4 h-100 text-decoration-none ec-action-card">
<div class="card-body d-flex align-items-center p-4">
<span class="d-inline-flex align-items-center justify-content-center rounded-3 bg-info bg-opacity-10 me-3" style="width:48px;height:48px;">
<i class="fa fa-bar-chart text-info"/>
</span>
<div>
<h6 class="fw-bold mb-0">View Results</h6>
<small class="text-muted">Track progress</small>
</div>
</div>
</a>
</div>
</div>
<!-- Courses Grid -->
<div id="courses_section" class="mb-5">
<h4 class="fw-bold mb-3"><i class="fa fa-graduation-cap me-2 text-primary"/>My Courses</h4>
<t t-if="courses">
<div class="row g-4">
<t t-foreach="courses" t-as="course">
<div class="col-md-6 col-lg-4">
<div class="card border-0 shadow-sm rounded-4 h-100 ec-course-card">
<div class="card-body p-4">
<h5 class="fw-bold mb-2" t-esc="course.name"/>
<p class="text-muted small mb-3" t-esc="course.code or 'No description'"/>
<div class="progress rounded-pill mb-2" style="height: 8px;">
<div class="progress-bar bg-primary rounded-pill" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"/>
</div>
<div class="d-flex justify-content-between align-items-center mt-3">
<small class="text-muted">0% complete</small>
<a t-attf-href="/my/course/#{course.id}" class="btn btn-sm btn-outline-primary rounded-3">Continue</a>
</div>
</div>
</div>
</div>
</t>
</div>
</t>
<t t-else="">
<div class="card border-0 bg-light rounded-4 p-4 text-center">
<p class="text-muted mb-0"><i class="fa fa-info-circle me-1"/>No courses enrolled yet. Complete your placement test to get started.</p>
</div>
</t>
</div>
<!-- Recent Results -->
<div id="results_section">
<h4 class="fw-bold mb-3"><i class="fa fa-trophy me-2 text-warning"/>Recent Exam Results</h4>
<t t-if="attempts">
<div class="table-responsive">
<table class="table table-hover align-middle bg-white rounded-4 overflow-hidden shadow-sm">
<thead class="table-light">
<tr>
<th>Exam</th>
<th>Date</th>
<th>Status</th>
<th class="text-end">Action</th>
</tr>
</thead>
<tbody>
<t t-foreach="attempts" t-as="a">
<tr>
<td class="fw-semibold" t-esc="a.display_name"/>
<td class="text-muted" t-esc="a.started_at" t-options='{"widget": "date"}'/>
<td>
<span t-attf-class="badge rounded-pill #{('bg-success' if a.status in ('released','scored') else 'bg-secondary')}" t-esc="a.status"/>
</td>
<td class="text-end">
<a t-attf-href="/my/exam/#{a.id}/results" class="btn btn-sm btn-outline-primary rounded-3">View</a>
</td>
</tr>
</t>
</tbody>
</table>
</div>
</t>
<t t-else="">
<div class="card border-0 bg-light rounded-4 p-4 text-center">
<p class="text-muted mb-0"><i class="fa fa-info-circle me-1"/>No exam results yet.</p>
</div>
</t>
</div>
</div>
</section>
</div>
</t>
</template>
<!-- ===================================================================
PLACEMENT BRIEFING
=================================================================== -->
<template id="placement_briefing" name="EnCoach Placement Briefing">
<t t-call="website.layout">
<div id="wrap" class="oe_structure">
<section class="ec-placement-section py-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-7 col-md-9">
<div class="card border-0 shadow-sm rounded-4">
<div class="card-body p-4 p-md-5">
<div class="text-center mb-4">
<span class="d-inline-flex align-items-center justify-content-center rounded-circle bg-primary bg-opacity-10 mb-3" style="width:80px;height:80px;">
<i class="fa fa-flask fa-2x text-primary"/>
</span>
<h2 class="fw-bold">Placement Test</h2>
<p class="text-muted">Discover your current proficiency level</p>
</div>
<div class="ec-test-overview mb-4">
<h5 class="fw-bold mb-3">What this test covers</h5>
<div class="row g-3">
<div class="col-6">
<div class="d-flex align-items-start">
<i class="fa fa-check-circle text-success mt-1 me-2"/>
<span>Reading Comprehension</span>
</div>
</div>
<div class="col-6">
<div class="d-flex align-items-start">
<i class="fa fa-check-circle text-success mt-1 me-2"/>
<span>Listening Skills</span>
</div>
</div>
<div class="col-6">
<div class="d-flex align-items-start">
<i class="fa fa-check-circle text-success mt-1 me-2"/>
<span>Grammar &amp; Vocabulary</span>
</div>
</div>
<div class="col-6">
<div class="d-flex align-items-start">
<i class="fa fa-check-circle text-success mt-1 me-2"/>
<span>Problem Solving</span>
</div>
</div>
</div>
</div>
<div class="ec-test-info bg-light rounded-3 p-3 mb-4">
<div class="row text-center">
<div class="col-4">
<div class="fw-bold text-primary fs-5">~25</div>
<small class="text-muted">Minutes</small>
</div>
<div class="col-4">
<div class="fw-bold text-primary fs-5">Adaptive</div>
<small class="text-muted">Difficulty</small>
</div>
<div class="col-4">
<div class="fw-bold text-primary fs-5">CEFR</div>
<small class="text-muted">A1 C2</small>
</div>
</div>
</div>
<h5 class="fw-bold mb-3">Before you begin</h5>
<ul class="list-unstyled mb-4">
<li class="d-flex align-items-start mb-2">
<span class="badge bg-primary bg-opacity-10 text-primary rounded-circle me-2 mt-1" style="width:24px;height:24px;line-height:24px;font-size:.75rem;">1</span>
<span>Find a quiet place with a stable internet connection</span>
</li>
<li class="d-flex align-items-start mb-2">
<span class="badge bg-primary bg-opacity-10 text-primary rounded-circle me-2 mt-1" style="width:24px;height:24px;line-height:24px;font-size:.75rem;">2</span>
<span>The test adapts to your answers — do your best on every question</span>
</li>
<li class="d-flex align-items-start mb-2">
<span class="badge bg-primary bg-opacity-10 text-primary rounded-circle me-2 mt-1" style="width:24px;height:24px;line-height:24px;font-size:.75rem;">3</span>
<span>You cannot pause or restart once you begin</span>
</li>
<li class="d-flex align-items-start">
<span class="badge bg-primary bg-opacity-10 text-primary rounded-circle me-2 mt-1" style="width:24px;height:24px;line-height:24px;font-size:.75rem;">4</span>
<span>Results are available immediately upon completion</span>
</li>
</ul>
<div class="text-center">
<a href="/my/placement/start" class="btn btn-primary btn-lg px-5 py-2 rounded-3 fw-semibold">
<i class="fa fa-play me-2"/>Begin Test
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</t>
</template>
<!-- ===================================================================
PLACEMENT RESULTS
=================================================================== -->
<template id="placement_results" name="EnCoach Placement Results">
<t t-call="website.layout">
<div id="wrap" class="oe_structure">
<section class="ec-results-section py-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8 col-md-10">
<!-- CEFR Level Badge -->
<div class="text-center mb-5">
<h2 class="fw-bold mb-3">Your Placement Results</h2>
<t t-if="session">
<div class="ec-cefr-badge d-inline-flex align-items-center justify-content-center rounded-circle bg-primary text-white shadow" style="width:120px;height:120px;">
<span class="fs-1 fw-bold" t-esc="session.final_cefr or '—'"/>
</div>
<p class="mt-3 text-muted">CEFR Level</p>
</t>
<t t-else="">
<div class="alert alert-warning rounded-3">No completed placement session found.</div>
</t>
</div>
<!-- Skill Radar Chart (OWL mount point) -->
<t t-if="abilities">
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-body p-4">
<h5 class="fw-bold mb-3">Skill Overview</h5>
<div id="ec_skill_radar_chart" class="ec-chart-container" style="height:300px;"
t-att-data-abilities="json.dumps([{'skill': a.skill_id.name, 'theta': a.theta} for a in abilities])"/>
</div>
</div>
<!-- Per-Skill Breakdown -->
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-body p-4">
<h5 class="fw-bold mb-3">Skill Breakdown</h5>
<t t-foreach="abilities" t-as="ability">
<div class="d-flex align-items-center mb-3">
<div class="me-3" style="min-width:120px;">
<span class="fw-semibold" t-esc="ability.skill_id.name or 'Skill'"/>
</div>
<div class="flex-grow-1">
<div class="progress rounded-pill" style="height:10px;">
<div class="progress-bar bg-primary rounded-pill" role="progressbar"
t-attf-style="width: #{min(max(int((ability.theta + 3) / 6 * 100), 0), 100)}%;"
t-att-aria-valuenow="ability.theta"/>
</div>
</div>
<div class="ms-3 text-end" style="min-width:60px;">
<span class="fw-semibold" t-esc="'%.2f' % ability.theta"/>
</div>
</div>
</t>
</div>
</div>
</t>
<!-- Recommended Path -->
<div class="card border-0 shadow-sm rounded-4">
<div class="card-body p-4 text-center">
<h5 class="fw-bold mb-2">Recommended Learning Path</h5>
<p class="text-muted mb-3">Based on your results, we've tailored a course path for you.</p>
<a href="/my/dashboard" class="btn btn-primary rounded-3 px-4">
<i class="fa fa-arrow-right me-2"/>Go to Dashboard
</a>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</t>
</template>
<!-- ===================================================================
COURSE DETAIL
=================================================================== -->
<template id="course_detail" name="EnCoach Course Detail">
<t t-call="website.layout">
<div id="wrap" class="oe_structure">
<section class="ec-course-section py-5">
<div class="container">
<!-- Course Header -->
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-body p-4 p-md-5">
<div class="row align-items-center">
<div class="col-md-8">
<h2 class="fw-bold mb-2" t-esc="course.name"/>
<p class="text-muted mb-3" t-esc="course.code or 'No description available.'"/>
</div>
<div class="col-md-4 text-md-end">
<div class="ec-progress-ring text-center">
<div class="fs-2 fw-bold text-primary">0%</div>
<small class="text-muted">Complete</small>
</div>
</div>
</div>
<div class="progress rounded-pill mt-3" style="height:10px;">
<div class="progress-bar bg-primary rounded-pill" role="progressbar" style="width:0%;"/>
</div>
</div>
</div>
<!-- Module List -->
<h4 class="fw-bold mb-3"><i class="fa fa-list-ul me-2 text-primary"/>Modules</h4>
<t t-if="modules">
<div class="ec-module-list">
<t t-foreach="modules" t-as="mod">
<div t-attf-class="card border-0 shadow-sm rounded-4 mb-3 #{('ec-module-locked' if mod.status == 'locked' else '')}">
<div class="card-body p-4">
<div class="d-flex align-items-center">
<div class="me-3">
<t t-if="mod.status == 'completed'">
<span class="d-inline-flex align-items-center justify-content-center rounded-circle bg-success text-white" style="width:40px;height:40px;">
<i class="fa fa-check"/>
</span>
</t>
<t t-elif="mod.status == 'locked'">
<span class="d-inline-flex align-items-center justify-content-center rounded-circle bg-secondary bg-opacity-25 text-secondary" style="width:40px;height:40px;">
<i class="fa fa-lock"/>
</span>
</t>
<t t-else="">
<span class="d-inline-flex align-items-center justify-content-center rounded-circle bg-primary bg-opacity-10 text-primary" style="width:40px;height:40px;">
<i class="fa fa-play"/>
</span>
</t>
</div>
<div class="flex-grow-1">
<h6 class="fw-bold mb-1" t-esc="mod.name"/>
<small class="text-muted" t-esc="mod.cefr_target or ''"/>
</div>
<div class="ms-3">
<t t-if="mod.status != 'locked'">
<span class="badge rounded-pill bg-primary bg-opacity-10 text-primary" t-esc="mod.status or 'available'"/>
</t>
</div>
</div>
</div>
</div>
</t>
</div>
</t>
<t t-else="">
<div class="card border-0 bg-light rounded-4 p-4 text-center">
<p class="text-muted mb-0">No modules available yet for this course.</p>
</div>
</t>
</div>
</section>
</div>
</t>
</template>
<!-- ===================================================================
EXAM RESULTS
=================================================================== -->
<template id="exam_results" name="EnCoach Exam Results">
<t t-call="website.layout">
<div id="wrap" class="oe_structure">
<section class="ec-exam-results-section py-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8 col-md-10">
<!-- Score Summary Card -->
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-body p-4 p-md-5 text-center">
<h2 class="fw-bold mb-3">Exam Results</h2>
<div class="row g-4 mb-4">
<div class="col-sm-4">
<div class="ec-score-highlight bg-primary bg-opacity-10 rounded-4 p-3">
<div class="fs-2 fw-bold text-primary" t-esc="'%.1f' % attempt.overall_band if attempt.overall_band else '—'"/>
<small class="text-muted d-block">Overall Band</small>
</div>
</div>
<div class="col-sm-4">
<div class="ec-score-highlight bg-success bg-opacity-10 rounded-4 p-3">
<div class="fs-2 fw-bold text-success" t-esc="attempt.cefr_level or '—'"/>
<small class="text-muted d-block">CEFR Level</small>
</div>
</div>
<div class="col-sm-4">
<div class="ec-score-highlight bg-info bg-opacity-10 rounded-4 p-3">
<div class="fs-2 fw-bold text-info" t-esc="attempt.status or '—'"/>
<small class="text-muted d-block">Status</small>
</div>
</div>
</div>
</div>
</div>
<!-- Skill Scores -->
<t t-if="scores">
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-body p-4">
<h5 class="fw-bold mb-3">Skill Scores</h5>
<t t-foreach="scores" t-as="score">
<div class="mb-3">
<div class="d-flex justify-content-between mb-1">
<span class="fw-semibold" t-esc="score.skill_name or score.display_name"/>
<span class="fw-bold text-primary" t-esc="'%.1f' % score.value if score.value else '—'"/>
</div>
<div class="progress rounded-pill" style="height:10px;">
<div class="progress-bar bg-primary rounded-pill" role="progressbar"
t-attf-style="width: #{min(int(score.value / 9.0 * 100), 100) if score.value else 0}%;"/>
</div>
</div>
</t>
</div>
</div>
</t>
<!-- Feedback -->
<t t-if="feedback">
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-body p-4">
<h5 class="fw-bold mb-3"><i class="fa fa-comments me-2 text-warning"/>Feedback</h5>
<t t-foreach="feedback" t-as="fb">
<div class="ec-feedback-item bg-light rounded-3 p-3 mb-2">
<strong t-esc="fb.skill_name or 'General'"/>
<p class="mb-0 mt-1 text-muted" t-esc="fb.text or fb.comment or ''"/>
</div>
</t>
</div>
</div>
</t>
<!-- Actions -->
<div class="d-flex flex-wrap gap-3 justify-content-center">
<a t-attf-href="/my/exam/#{attempt.id}/pdf" class="btn btn-outline-primary rounded-3 px-4">
<i class="fa fa-download me-2"/>Download PDF
</a>
<t t-if="attempt.verification_hash">
<a t-attf-href="/verify/#{attempt.verification_hash}" class="btn btn-outline-secondary rounded-3 px-4" target="_blank">
<i class="fa fa-qrcode me-2"/>Verify Score
</a>
</t>
<a href="/my/dashboard" class="btn btn-primary rounded-3 px-4">
<i class="fa fa-arrow-left me-2"/>Back to Dashboard
</a>
</div>
</div>
</div>
</div>
</section>
</div>
</t>
</template>
<!-- ===================================================================
RESULTS PENDING
=================================================================== -->
<template id="results_pending" name="EnCoach Results Pending">
<t t-call="website.layout">
<div id="wrap" class="oe_structure">
<section class="ec-pending-section py-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-6 col-md-8 text-center">
<div class="card border-0 shadow-sm rounded-4">
<div class="card-body p-5">
<span class="d-inline-flex align-items-center justify-content-center rounded-circle bg-warning bg-opacity-10 mb-4" style="width:80px;height:80px;">
<i class="fa fa-clock-o fa-2x text-warning"/>
</span>
<h3 class="fw-bold mb-2">Results Not Yet Released</h3>
<p class="text-muted mb-4">
Your exam (<strong t-esc="attempt.display_name"/>) is still being processed.
Please check back later.
</p>
<div class="d-flex gap-3 justify-content-center">
<a href="/my/dashboard" class="btn btn-primary rounded-3 px-4">
<i class="fa fa-arrow-left me-2"/>Dashboard
</a>
<button class="btn btn-outline-secondary rounded-3 px-4" onclick="location.reload();">
<i class="fa fa-refresh me-2"/>Refresh
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</t>
</template>
<!-- ===================================================================
PROFILE PAGE
=================================================================== -->
<template id="profile" name="EnCoach Profile">
<t t-call="website.layout">
<div id="wrap" class="oe_structure">
<section class="ec-profile-section py-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-7 col-md-9">
<h2 class="fw-bold mb-4">My Profile</h2>
<!-- Profile Form -->
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-body p-4">
<h5 class="fw-bold mb-3">Personal Information</h5>
<form action="/my/profile" method="post">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="row g-3">
<div class="col-md-6">
<label for="prof_name" class="form-label fw-semibold">Full Name</label>
<input type="text" id="prof_name" name="name" class="form-control rounded-3" t-att-value="user.name" required="required"/>
</div>
<div class="col-md-6">
<label for="prof_email" class="form-label fw-semibold">Email</label>
<input type="email" id="prof_email" class="form-control rounded-3 bg-light" t-att-value="user.email" readonly="readonly"/>
</div>
<div class="col-md-6">
<label for="prof_phone" class="form-label fw-semibold">Phone</label>
<input type="tel" id="prof_phone" name="phone" class="form-control rounded-3" t-att-value="user.phone or ''"/>
</div>
<div class="col-md-6">
<label for="prof_prefs" class="form-label fw-semibold">Study Preferences</label>
<select id="prof_prefs" name="study_preference" class="form-select rounded-3">
<option value="self_paced" t-att-selected="profile and profile.study_mode == 'self_paced'">Self-paced</option>
<option value="structured" t-att-selected="profile and profile.study_mode == 'structured'">Structured</option>
<option value="intensive" t-att-selected="profile and profile.study_mode == 'intensive'">Intensive</option>
</select>
</div>
</div>
<button type="submit" class="btn btn-primary rounded-3 mt-4 px-4">
<i class="fa fa-save me-2"/>Save Changes
</button>
</form>
</div>
</div>
<!-- Change Password -->
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-body p-4">
<h5 class="fw-bold mb-3">Change Password</h5>
<form action="/my/profile/password" method="post">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="mb-3">
<label for="current_pw" class="form-label fw-semibold">Current Password</label>
<input type="password" id="current_pw" name="current_password" class="form-control rounded-3" required="required"/>
</div>
<div class="mb-3">
<label for="new_pw" class="form-label fw-semibold">New Password</label>
<input type="password" id="new_pw" name="new_password" class="form-control rounded-3" required="required" minlength="8"/>
</div>
<div class="mb-3">
<label for="confirm_pw" class="form-label fw-semibold">Confirm New Password</label>
<input type="password" id="confirm_pw" name="confirm_new_password" class="form-control rounded-3" required="required"/>
</div>
<button type="submit" class="btn btn-outline-primary rounded-3 px-4">
<i class="fa fa-key me-2"/>Update Password
</button>
</form>
</div>
</div>
<!-- Entity Info -->
<t t-if="profile and profile.entity_id">
<div class="card border-0 shadow-sm rounded-4">
<div class="card-body p-4">
<h5 class="fw-bold mb-3">Organisation</h5>
<div class="d-flex align-items-center">
<span class="d-inline-flex align-items-center justify-content-center rounded-3 bg-primary bg-opacity-10 me-3" style="width:48px;height:48px;">
<i class="fa fa-building text-primary"/>
</span>
<div>
<h6 class="fw-bold mb-0" t-esc="profile.entity_id.name"/>
<small class="text-muted" t-esc="profile.entity_id.name or ''"/>
</div>
</div>
</div>
</div>
</t>
</div>
</div>
</div>
</section>
</div>
</t>
</template>
<!-- ===================================================================
SCORE VERIFICATION (PUBLIC)
=================================================================== -->
<template id="score_verification" name="EnCoach Score Verification">
<t t-call="website.layout">
<div id="wrap" class="oe_structure">
<section class="ec-verification-section py-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-6 col-md-8">
<div class="card border-0 shadow-sm rounded-4">
<div class="card-body p-4 p-md-5 text-center">
<span class="d-inline-flex align-items-center justify-content-center rounded-circle bg-success bg-opacity-10 mb-4" style="width:80px;height:80px;">
<i class="fa fa-shield fa-2x text-success"/>
</span>
<h2 class="fw-bold mb-2">Score Verification</h2>
<p class="text-muted mb-4">Verify the authenticity of an EnCoach score report</p>
<div class="bg-light rounded-3 p-3 mb-4">
<small class="text-muted d-block mb-1">Verification Code</small>
<code class="fs-5" t-esc="hash_code"/>
</div>
<!-- Verification result loaded dynamically via JS/RPC -->
<div id="ec_verification_result">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Verifying…</span>
</div>
<p class="text-muted mt-2">Looking up results…</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</t>
</template>
</odoo>