Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
a5ec6f6
spec: define volunteer work entities and rules in Allium
rorar Apr 13, 2026
7653587
feat(backend): implement volunteer work table, API endpoints and ATS …
rorar Apr 13, 2026
9cde49f
feat(frontend): add volunteer work section to templates and shared re…
rorar Apr 13, 2026
36c20ed
feat(admin): implement volunteer management UI and role-based modal
rorar Apr 13, 2026
50928e0
i18n: add volunteer work translations for all languages
rorar Apr 13, 2026
925b697
test: add backend and integration tests for volunteer work
rorar Apr 13, 2026
756b132
fix(volunteer): add missing i18n keys and fix action button endpoints
rorar Apr 13, 2026
85b1a40
Fix timeline volunteer visibility and ordering
rorar Apr 13, 2026
2d54fdf
Add volunteer public timeline regressions
rorar Apr 13, 2026
7e30018
fix(public): renderVolunteerFromData called with wrong property name
rorar Apr 13, 2026
0e3e525
fix(allium): add volunteer_work to dataset rules and PublicCV surface
rorar Apr 13, 2026
cbb7c54
security: XSS, path traversal, input validation, and performance fixes
rorar Apr 13, 2026
02dc31a
chore: add .full-review/ and .full-stack-feature/ to gitignore
rorar Apr 13, 2026
b6e0f90
fix(low): S12 dataset authorization, CQ4 Schema.org markup, CQ5 timel…
rorar Apr 14, 2026
bd3644a
chore: add .codex to gitignore
rorar Apr 14, 2026
b4679a2
i18n: add form.description_optional to all language files
rorar Apr 14, 2026
f8d3060
fix: use t('present') for volunteer role end date placeholders
rorar Apr 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ claude.md
docker-compose.unraid.yml
docker-compose.yml
/node_modules
/site
/site
/.full-review/
/.full-stack-feature/
/.full-review-archive/
/.full-stack-feature-archive/
/.codex
62 changes: 47 additions & 15 deletions public-readonly/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ <h2 class="section-title" data-i18n="section.experience">Work Experience</h2>
<div class="items-list" id="experienceList"></div>
</section>

<!-- Volunteer Work Section -->
<section class="section" id="section-volunteer">
<div class="section-header">
<h2 class="section-title" data-i18n="section.volunteer">Volunteer Work</h2>
</div>
<div class="items-list" id="volunteerList"></div>
</section>

<!-- Certifications Section -->
<section class="section" id="section-certifications">
<div class="section-header">
Expand Down Expand Up @@ -154,6 +162,7 @@ <h2 class="section-title" data-i18n="section.projects">Featured Projects</h2>
await loadProfile(false); // false = don't include email/phone
await loadTimeline();
await loadExperiencesReadOnly();
await loadVolunteerReadOnly();
await loadCertificationsReadOnly();
await loadEducationReadOnly();
await loadSkillsReadOnly();
Expand Down Expand Up @@ -197,11 +206,12 @@ <h2 class="section-title" data-i18n="section.projects">Featured Projects</h2>
if (data.timeline && data.timeline.length > 0) {
renderTimelineItems(data.timeline, { interactive: true });
} else {
renderTimelineFromData(data.experiences, data.customSections);
renderTimelineFromData(data.experiences, data.volunteer, data.customSections, data.sectionVisibility);
}

// Render sections
renderExperiencesFromData(data.experiences);
renderVolunteerFromData(data.volunteer_work);
renderCertificationsFromData(data.certifications);
renderEducationFromData(data.education);
renderSkillsFromData(data.skills);
Expand Down Expand Up @@ -292,8 +302,9 @@ <h2 class="section-title" data-i18n="section.projects">Featured Projects</h2>

// Render from dataset data
renderProfileFromData(datasetData.profile);
renderTimelineFromData(datasetData.experiences, datasetData.customSections);
renderTimelineFromData(datasetData.experiences, datasetData.volunteer_work, datasetData.customSections, datasetData.sectionVisibility);
renderExperiencesFromData(datasetData.experiences);
renderVolunteerFromData(datasetData.volunteer_work);
renderCertificationsFromData(datasetData.certifications);
renderEducationFromData(datasetData.education);
renderSkillsFromData(datasetData.skills);
Expand Down Expand Up @@ -418,20 +429,40 @@ <h2 class="section-title" data-i18n="section.projects">Featured Projects</h2>
return 0;
}

function renderTimelineFromData(experiences, customSections) {
if (!experiences) return;
const visible = experiences.filter(e => e.visible !== false);
function renderTimelineFromData(experiences, volunteer, customSections, sectionVisibility) {
const normalized = [];

if (sectionVisibility?.experience !== false && experiences) {
experiences.filter(exp => exp.visible !== false).forEach(exp => {
normalized.push({
id: exp.id,
company: exp.company_name,
role: exp.job_title,
countryCode: exp.country_code || '',
logo: exp.logo_filename || null,
start_date: exp.start_date,
end_date: exp.end_date,
visible: exp.visible
});
});
}

const normalized = visible.map(exp => ({
id: exp.id,
company: exp.company_name,
role: exp.job_title,
countryCode: exp.country_code || '',
logo: exp.logo_filename || null,
start_date: exp.start_date,
end_date: exp.end_date,
visible: exp.visible
}));
if (sectionVisibility?.volunteer !== false && volunteer) {
volunteer.filter(entry => entry.visible !== false).forEach(entry => {
(entry.roles || []).forEach((role, ridx) => {
normalized.push({
id: `vol_${entry.id || entry.organization}_${ridx}`,
company: entry.organization,
role: role.title || '',
countryCode: '',
logo: null,
start_date: role.start_date,
end_date: role.end_date,
visible: entry.visible
});
});
});
}

// Merge timeline-layout custom section items
if (customSections) {
Expand Down Expand Up @@ -793,6 +824,7 @@ <h3 class="project-title" itemprop="name">${escapeHtml(proj.title)}</h3>
'about': document.getElementById('section-about'),
'timeline': document.getElementById('section-timeline'),
'experience': document.getElementById('section-experience'),
'volunteer': document.getElementById('section-volunteer'),
'certifications': document.getElementById('section-certifications'),
'education': document.getElementById('section-education'),
'skills': document.getElementById('section-skills'),
Expand Down
17 changes: 17 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,23 @@ <h2 class="section-title" data-i18n="section.experience">Work Experience</h2>
</button>
</section>

<!-- Volunteer Work Section -->
<section class="section" id="section-volunteer">
<div class="section-header">
<h2 class="section-title" data-i18n="section.volunteer">Volunteer Work</h2>
<div class="section-actions no-print">
<button class="icon-btn" onclick="toggleSection('volunteer')" data-i18n-title="action.toggle_visibility" title="Toggle Visibility" id="toggle-volunteer">
<span class="material-symbols-outlined">visibility</span>
</button>
</div>
</div>
<div class="items-list" id="volunteerList"></div>
<button class="add-btn no-print" onclick="openModal('volunteer')">
<span class="material-symbols-outlined">add</span>
<span data-i18n="action.add_volunteer">Add Volunteer Work</span>
</button>
</section>

<!-- Certifications Section -->
<section class="section" id="section-certifications">
<div class="section-header">
Expand Down
Loading