feat: implement carousel-based timeline view for internship dashboard commit history

This commit is contained in:
Ümit Tunç
2026-06-06 01:10:17 +03:00
parent db60909348
commit 99e97edfd1
@@ -394,8 +394,42 @@
Henüz hiç commit bulunamadı veya deponuz tanımlanmadı. Lütfen deponuzu tanımlayın ve commitlerinizi gönderin. Henüz hiç commit bulunamadı veya deponuz tanımlanmadı. Lütfen deponuzu tanımlayın ve commitlerinizi gönderin.
</div> </div>
<div id="commits-timeline" class="hidden grid grid-cols-1 sm:grid-cols-2 gap-6"> <!-- Day Paginator / Navbar -->
<!-- Loaded dynamically by JS --> <div id="journal-carousel-nav" class="hidden flex items-center justify-between gap-4 p-3 bg-slate-50/80 border border-slate-100 rounded-2xl mb-6 select-none">
<button type="button" id="prev-day-btn" onclick="navigateDay(-1)" class="w-10 h-10 rounded-xl bg-white hover:bg-blue-50 text-slate-600 hover:text-blue-600 border border-slate-200/60 hover:border-blue-100 flex items-center justify-center transition-all shadow-sm">
<i class="uil uil-angle-left text-xl"></i>
</button>
<div class="flex-grow overflow-x-auto no-scrollbar py-1">
<div id="day-tabs-container" class="flex gap-2 justify-start sm:justify-center min-w-max px-2">
<!-- Day tabs rendered dynamically -->
</div>
</div>
<button type="button" id="next-day-btn" onclick="navigateDay(1)" class="w-10 h-10 rounded-xl bg-white hover:bg-blue-50 text-slate-600 hover:text-blue-600 border border-slate-200/60 hover:border-blue-100 flex items-center justify-center transition-all shadow-sm">
<i class="uil uil-angle-right text-xl"></i>
</button>
</div>
<!-- Day Content Timeline Container (Carousel View) -->
<div id="commits-timeline-container" class="hidden">
<div id="active-day-card" class="bg-white border border-slate-100/80 rounded-2xl p-6 shadow-sm transition-all duration-300 transform">
<!-- Active Day Details (Date / Day Title) -->
<div class="flex items-center justify-between mb-8 pb-4 border-b border-slate-100">
<div class="flex items-center gap-3">
<span id="active-day-title" class="text-base font-extrabold text-blue-600 bg-blue-50 px-4 py-1.5 rounded-full">1. Gün</span>
<span id="active-day-date" class="text-sm font-semibold text-slate-400"><i class="uil uil-calendar-alt mr-1"></i> 29.05.2026</span>
</div>
<span id="active-day-commit-count" class="text-xs font-bold text-slate-400 bg-slate-50 border border-slate-100 px-2.5 py-1 rounded-lg">3 Commit</span>
</div>
<!-- Timeline items list -->
<div class="relative pl-6 sm:pl-8 before:content-[''] before:absolute before:left-[11px] sm:before:left-[15px] before:top-2 before:bottom-2 before:w-[2px] before:bg-slate-100">
<div id="timeline-events" class="space-y-6">
<!-- Event items rendered dynamically -->
</div>
</div>
</div>
</div> </div>
</div> </div>
@@ -459,12 +493,15 @@
}); });
const githubRepoUrl = @json($intern->github_repo); const githubRepoUrl = @json($intern->github_repo);
let commitDays = [];
let activeDayIndex = 0;
function loadGithubCommits() { function loadGithubCommits() {
const loader = document.getElementById('commits-loader'); const loader = document.getElementById('commits-loader');
const errorEl = document.getElementById('commits-error'); const errorEl = document.getElementById('commits-error');
const emptyEl = document.getElementById('commits-empty'); const emptyEl = document.getElementById('commits-empty');
const timelineEl = document.getElementById('commits-timeline'); const navEl = document.getElementById('journal-carousel-nav');
const containerEl = document.getElementById('commits-timeline-container');
if (!githubRepoUrl) { if (!githubRepoUrl) {
emptyEl.classList.remove('hidden'); emptyEl.classList.remove('hidden');
@@ -474,8 +511,9 @@
loader.classList.remove('hidden'); loader.classList.remove('hidden');
errorEl.classList.add('hidden'); errorEl.classList.add('hidden');
emptyEl.classList.add('hidden'); emptyEl.classList.add('hidden');
timelineEl.classList.add('hidden'); navEl.classList.add('hidden');
timelineEl.innerHTML = ''; containerEl.classList.add('hidden');
commitDays = [];
// Parse owner and repo // Parse owner and repo
let repoClean = githubRepoUrl.replace(/https?:\/\/(www\.)?github\.com\//i, ''); let repoClean = githubRepoUrl.replace(/https?:\/\/(www\.)?github\.com\//i, '');
@@ -528,53 +566,27 @@
const sortedDates = Object.keys(grouped).sort(); const sortedDates = Object.keys(grouped).sort();
timelineEl.classList.remove('hidden'); commitDays = sortedDates.map((date, index) => {
sortedDates.forEach((date, index) => {
const dayNum = index + 1;
const dayCommits = grouped[date];
// Format date as DD.MM.YYYY
const dateParts = date.split('-'); const dateParts = date.split('-');
const formattedDate = `${dateParts[2]}.${dateParts[1]}.${dateParts[0]}`; return {
date: date,
// Generate commits list HTML dayNum: index + 1,
let commitsListHtml = ''; formattedDate: `${dateParts[2]}.${dateParts[1]}.${dateParts[0]}`,
dayCommits.forEach(c => { commits: grouped[date]
const message = c.commit.message; };
const authorDate = new Date(c.commit.author.date);
const timeStr = authorDate.toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' });
const shaShort = c.sha.substring(0, 7);
const commitUrl = c.html_url;
commitsListHtml += `
<div class="p-3 bg-white rounded-xl border border-slate-100 hover:border-slate-200 transition-all shadow-sm">
<div class="flex items-center justify-between gap-2 mb-1.5">
<span class="text-[10px] font-bold text-slate-400"><i class="uil uil-clock"></i> ${timeStr}</span>
<a href="${commitUrl}" target="_blank" class="text-[10px] font-mono font-bold text-slate-500 hover:text-blue-600 bg-slate-50 px-2 py-0.5 rounded border border-slate-100 transition-colors">
${shaShort}
</a>
</div>
<p class="text-xs font-bold text-slate-700 leading-relaxed whitespace-pre-line">${escapeHtml(message)}</p>
</div>
`;
});
const dayCardHtml = `
<div class="bg-slate-50/60 border border-slate-100/80 hover:border-blue-100 hover:bg-blue-50/10 rounded-2xl p-5 transition-all flex flex-col justify-between">
<div>
<div class="flex items-center justify-between mb-4 border-b border-slate-100 pb-2.5">
<span class="text-sm font-extrabold text-blue-600 bg-blue-50/60 px-3 py-1 rounded-full">${dayNum}. Gün</span>
<span class="text-xs font-bold text-slate-400"><i class="uil uil-calendar-alt"></i> ${formattedDate}</span>
</div>
<div class="space-y-3">
${commitsListHtml}
</div>
</div>
</div>
`;
timelineEl.insertAdjacentHTML('beforeend', dayCardHtml);
}); });
if (commitDays.length === 0) {
emptyEl.classList.remove('hidden');
return;
}
activeDayIndex = 0;
navEl.classList.remove('hidden');
containerEl.classList.remove('hidden');
renderDayTabs();
renderActiveDay();
}) })
.catch(err => { .catch(err => {
loader.classList.add('hidden'); loader.classList.add('hidden');
@@ -583,6 +595,109 @@
}); });
} }
function renderDayTabs() {
const container = document.getElementById('day-tabs-container');
container.innerHTML = '';
commitDays.forEach((day, index) => {
const isActive = index === activeDayIndex;
const activeClass = isActive
? 'text-blue-700 bg-blue-50 border border-blue-200 font-extrabold shadow-sm'
: 'bg-white hover:bg-slate-50 text-slate-600 border border-slate-200/60 font-semibold hover:text-blue-600';
const tab = document.createElement('button');
tab.type = 'button';
tab.className = `px-5 py-2 rounded-xl transition-all flex flex-col items-center justify-center ${activeClass}`;
tab.innerHTML = `
<span class="text-xs">${day.dayNum}. Gün</span>
<span class="text-[9px] mt-0.5 opacity-70 font-medium">${day.formattedDate}</span>
`;
tab.onclick = () => {
activeDayIndex = index;
renderDayTabs();
renderActiveDay();
};
container.appendChild(tab);
});
// Handle scroll to active element
const activeTab = container.children[activeDayIndex];
if (activeTab) {
activeTab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
}
// Disable/enable arrows
const prevBtn = document.getElementById('prev-day-btn');
const nextBtn = document.getElementById('next-day-btn');
prevBtn.disabled = activeDayIndex === 0;
prevBtn.classList.toggle('opacity-40', activeDayIndex === 0);
prevBtn.classList.toggle('cursor-not-allowed', activeDayIndex === 0);
nextBtn.disabled = activeDayIndex === commitDays.length - 1;
nextBtn.classList.toggle('opacity-40', activeDayIndex === commitDays.length - 1);
nextBtn.classList.toggle('cursor-not-allowed', activeDayIndex === commitDays.length - 1);
}
function renderActiveDay() {
const card = document.getElementById('active-day-card');
// Smooth transition
card.style.opacity = '0.3';
card.style.transform = 'translateY(5px)';
setTimeout(() => {
const day = commitDays[activeDayIndex];
document.getElementById('active-day-title').textContent = `${day.dayNum}. Gün`;
document.getElementById('active-day-date').innerHTML = `<i class="uil uil-calendar-alt mr-1"></i> ${day.formattedDate}`;
document.getElementById('active-day-commit-count').textContent = `${day.commits.length} Commit`;
const eventsContainer = document.getElementById('timeline-events');
eventsContainer.innerHTML = '';
day.commits.forEach(c => {
const message = c.commit.message;
const authorDate = new Date(c.commit.author.date);
const timeStr = authorDate.toLocaleTimeString('tr-TR', { hour: '2-digit', minute: '2-digit' });
const shaShort = c.sha.substring(0, 7);
const commitUrl = c.html_url;
const eventHtml = `
<div class="relative group">
<!-- Timeline Dot -->
<div class="absolute -left-[31px] sm:-left-[39px] top-1.5 w-6 h-6 rounded-full bg-white border-2 border-blue-500 flex items-center justify-center transition-all group-hover:bg-blue-500">
<div class="w-2 h-2 rounded-full bg-blue-500 group-hover:bg-white transition-all"></div>
</div>
<!-- Event Card -->
<div class="p-4 bg-slate-50/50 hover:bg-blue-50/10 border border-slate-100 hover:border-blue-100/60 rounded-2xl transition-all shadow-sm">
<div class="flex items-center justify-between gap-4 mb-2">
<span class="text-xs font-bold text-slate-400"><i class="uil uil-clock mr-1"></i> ${timeStr}</span>
<a href="${commitUrl}" target="_blank" class="text-xs font-mono font-bold text-blue-600 hover:text-blue-700 bg-blue-50/60 hover:bg-blue-50 px-2.5 py-0.5 rounded-lg border border-blue-100/40 transition-colors">
${shaShort}
</a>
</div>
<p class="text-sm font-bold text-slate-700 leading-relaxed whitespace-pre-line">${escapeHtml(message)}</p>
</div>
</div>
`;
eventsContainer.insertAdjacentHTML('beforeend', eventHtml);
});
card.style.opacity = '1';
card.style.transform = 'translateY(0)';
}, 150);
}
function navigateDay(dir) {
const newIndex = activeDayIndex + dir;
if (newIndex >= 0 && newIndex < commitDays.length) {
activeDayIndex = newIndex;
renderDayTabs();
renderActiveDay();
}
}
function escapeHtml(text) { function escapeHtml(text) {
if (!text) return ''; if (!text) return '';
return text return text
@@ -679,6 +794,13 @@
.tab-btn.active span.text-slate-800 { .tab-btn.active span.text-slate-800 {
color: #1e40af !important; color: #1e40af !important;
} }
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
</style> </style>
@endpush @endpush
@endsection @endsection