feat: implement carousel-based timeline view for internship dashboard commit history
This commit is contained in:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user