feat: add GitHub username field and implement commit filtering by author in intern dashboard
This commit is contained in:
@@ -208,22 +208,35 @@ class CareerController extends Controller
|
||||
|
||||
$request->validate([
|
||||
'github_repo' => 'required|string|url|max:255',
|
||||
'github_username' => 'nullable|string|max:100',
|
||||
], [
|
||||
'github_repo.required' => 'Lütfen Github depo URL\'sini girin.',
|
||||
'github_repo.url' => 'Geçerli bir URL girilmelidir.',
|
||||
'github_repo.max' => 'Github depo URL\'si en fazla 255 karakter olabilir.',
|
||||
'github_username.max' => 'Github kullanıcı adı en fazla 100 karakter olabilir.',
|
||||
]);
|
||||
|
||||
$intern = CareerApplication::findOrFail(session('intern_id'));
|
||||
|
||||
// Sanitize & format github URL
|
||||
$repoUrl = trim($request->github_repo);
|
||||
$username = trim($request->github_username);
|
||||
|
||||
$intern->update([
|
||||
'github_repo' => $repoUrl
|
||||
'github_repo' => $repoUrl,
|
||||
'github_username' => $username ?: null,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', 'Github deposu başarıyla güncellendi.');
|
||||
if ($request->expectsJson() || $request->ajax()) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Github bilgileri başarıyla güncellendi.',
|
||||
'github_repo' => $repoUrl,
|
||||
'github_username' => $username ?: null,
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', 'Github bilgileri başarıyla güncellendi.');
|
||||
}
|
||||
|
||||
public function downloadMarkdown()
|
||||
|
||||
@@ -30,6 +30,7 @@ class CareerApplication extends Model
|
||||
'internship_end_date',
|
||||
'internship_total_days',
|
||||
'github_repo',
|
||||
'github_username',
|
||||
'certificate_code',
|
||||
'transcript_markdown',
|
||||
'notebook_supervisor_signed',
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->string('github_username')->nullable()->after('github_repo');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->dropColumn('github_username');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -655,15 +655,23 @@
|
||||
<div class="p-5 rounded-2xl border border-slate-100 bg-slate-50/50 mb-6">
|
||||
<form action="{{ route('intern.save-github') }}" method="POST" class="space-y-4">
|
||||
@csrf
|
||||
<div>
|
||||
<label for="github_repo" class="block text-xs font-extrabold text-slate-500 uppercase tracking-wider mb-2">GitHub Public Depo URL'si</label>
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="github_repo" class="block text-xs font-extrabold text-slate-500 uppercase tracking-wider mb-2">GitHub Public Depo URL'si</label>
|
||||
<input type="url" name="github_repo" id="github_repo" placeholder="https://github.com/kullaniciadi/depo-adi" value="{{ $intern->github_repo }}" required class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 focus:outline-none text-sm font-semibold text-slate-700 bg-white transition-all" />
|
||||
<button type="submit" class="px-5 py-3 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-sm transition-all shadow-md shadow-blue-500/10 flex-shrink-0">
|
||||
{{ $intern->github_repo ? 'Güncelle' : 'Kaydet' }}
|
||||
</button>
|
||||
</div>
|
||||
<span class="block text-xs text-slate-400 mt-2"><i class="uil uil-info-circle text-blue-600"></i> Deponuzun <strong>public (herkese açık)</strong> olması gerekmektedir.</span>
|
||||
<div>
|
||||
<label for="github_username" class="block text-xs font-extrabold text-slate-500 uppercase tracking-wider mb-2">GitHub Kullanıcı Adınız (Opsiyonel)</label>
|
||||
<input type="text" name="github_username" id="github_username" placeholder="GitHub kullanıcı adınız (örn: Emresatil)" value="{{ $intern->github_username }}" class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 focus:outline-none text-sm font-semibold text-slate-700 bg-white transition-all" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 pt-2 border-t border-slate-100">
|
||||
<span class="text-xs text-slate-400 leading-normal max-w-xl">
|
||||
<i class="uil uil-info-circle text-blue-600 text-sm"></i> Deponuzun <strong>public</strong> olması gerekir. Birden fazla kişi aynı projede çalışıyorsa, kendi commitlerinizi süzmek için kullanıcı adınızı yazmanız önerilir.
|
||||
</span>
|
||||
<button type="submit" class="px-5 py-3 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-sm transition-all shadow-md shadow-blue-500/10 flex-shrink-0 w-full sm:w-auto">
|
||||
Bilgileri Kaydet
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -822,6 +830,39 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Author Selection Modal -->
|
||||
<div id="author-modal" class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
|
||||
<div class="bg-white rounded-3xl p-6 max-w-md w-full shadow-2xl border border-slate-100/50 transform scale-95 opacity-0 transition-all duration-300" id="author-modal-card">
|
||||
<div class="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-slate-800 flex items-center gap-2">
|
||||
<i class="uil uil-users-alt text-blue-600"></i>
|
||||
<span>GitHub Yazar Seçimi</span>
|
||||
</h3>
|
||||
<p class="text-xs text-slate-400 mt-1">Bu günün commitlerinde birden fazla yazar tespit edildi. Hangisi sizsiniz?</p>
|
||||
</div>
|
||||
<button onclick="closeAuthorModal()" class="text-slate-400 hover:text-slate-600">
|
||||
<i class="uil uil-multiply text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="author-list-container" class="space-y-2 mb-4 max-h-[220px] overflow-y-auto pr-1 no-scrollbar">
|
||||
<!-- Dynamically populated author options -->
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 mb-5 bg-slate-50 p-3 rounded-xl border border-slate-200/60">
|
||||
<input type="checkbox" id="save-selected-author-pref" class="rounded text-blue-600 focus:ring-blue-500 w-4 h-4" checked />
|
||||
<label for="save-selected-author-pref" class="text-xs text-slate-600 font-bold select-none cursor-pointer">
|
||||
Seçtiğim kullanıcı adını profilime kaydet ve varsayılan yap
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="button" onclick="closeAuthorModal()" class="px-4 py-2 bg-slate-100 hover:bg-slate-200 text-slate-600 font-bold rounded-xl text-xs transition-all">İptal</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('styles')
|
||||
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"></script>
|
||||
@@ -888,8 +929,148 @@
|
||||
});
|
||||
|
||||
const githubRepoUrl = @json($intern->github_repo);
|
||||
let githubUsername = @json($intern->github_username);
|
||||
let activeDayIdx = 0;
|
||||
|
||||
let activeCommitsToProcess = [];
|
||||
let activeCommitsDateVal = '';
|
||||
|
||||
function openAuthorModal(uniqueAuthors) {
|
||||
const modal = document.getElementById('author-modal');
|
||||
const card = document.getElementById('author-modal-card');
|
||||
const container = document.getElementById('author-list-container');
|
||||
if (!modal || !card || !container) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
// 1. Add "Hepsi" (All commits) option
|
||||
const allBtn = document.createElement('button');
|
||||
allBtn.type = 'button';
|
||||
allBtn.className = "w-full p-3 rounded-xl border border-slate-200 hover:border-blue-400 hover:bg-blue-50/20 text-left transition-all font-extrabold text-xs text-slate-700 flex items-center justify-between";
|
||||
allBtn.innerHTML = `
|
||||
<span>Tüm Commitler (Filtreleme Yapma)</span>
|
||||
<i class="uil uil-angle-right text-base text-slate-400"></i>
|
||||
`;
|
||||
allBtn.addEventListener('click', () => {
|
||||
insertCommitsForAuthor(null);
|
||||
});
|
||||
container.appendChild(allBtn);
|
||||
|
||||
// 2. Add each unique author option
|
||||
uniqueAuthors.forEach(author => {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = "w-full p-3 rounded-xl border border-slate-200 hover:border-blue-400 hover:bg-blue-50/20 text-left transition-all font-extrabold text-xs text-slate-700 flex items-center justify-between mt-2";
|
||||
btn.innerHTML = `
|
||||
<span>${author}</span>
|
||||
<i class="uil uil-angle-right text-base text-slate-400"></i>
|
||||
`;
|
||||
btn.addEventListener('click', () => {
|
||||
insertCommitsForAuthor(author);
|
||||
});
|
||||
container.appendChild(btn);
|
||||
});
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
setTimeout(() => {
|
||||
card.classList.remove('scale-95', 'opacity-0');
|
||||
card.classList.add('scale-100', 'opacity-100');
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function closeAuthorModal() {
|
||||
const modal = document.getElementById('author-modal');
|
||||
const card = document.getElementById('author-modal-card');
|
||||
if (!modal || !card) return;
|
||||
|
||||
card.classList.remove('scale-100', 'opacity-100');
|
||||
card.classList.add('scale-95', 'opacity-0');
|
||||
setTimeout(() => {
|
||||
modal.classList.add('hidden');
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function insertCommitsForAuthor(author) {
|
||||
closeAuthorModal();
|
||||
|
||||
const savePref = document.getElementById('save-selected-author-pref');
|
||||
if (author && savePref && savePref.checked) {
|
||||
saveGithubUsernamePreference(author);
|
||||
}
|
||||
|
||||
let dayCommits = activeCommitsToProcess;
|
||||
if (author) {
|
||||
dayCommits = activeCommitsToProcess.filter(item => {
|
||||
const commitAuthorName = item.commit.author.name;
|
||||
const commitAuthorLogin = item.author ? item.author.login : null;
|
||||
return (commitAuthorLogin && commitAuthorLogin.toLowerCase() === author.toLowerCase()) ||
|
||||
(commitAuthorName && commitAuthorName.toLowerCase() === author.toLowerCase());
|
||||
});
|
||||
}
|
||||
|
||||
renderCommitsToEditor(dayCommits);
|
||||
}
|
||||
|
||||
function saveGithubUsernamePreference(username) {
|
||||
if (!githubRepoUrl) return;
|
||||
|
||||
fetch("{{ route('intern.save-github') }}", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-CSRF-TOKEN": "{{ csrf_token() }}"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
github_repo: githubRepoUrl,
|
||||
github_username: username
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
githubUsername = username;
|
||||
const usernameInput = document.getElementById('github_username');
|
||||
if (usernameInput) {
|
||||
usernameInput.value = username;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.warn("Kullanıcı adı tercihi kaydedilemedi.", err);
|
||||
});
|
||||
}
|
||||
|
||||
function renderCommitsToEditor(commitsList) {
|
||||
if (commitsList.length === 0) {
|
||||
alert("Bu yazar için seçilen güne ait commit bulunamadı.");
|
||||
return;
|
||||
}
|
||||
|
||||
let commitHtml = "<ul>";
|
||||
const sorted = [...commitsList].reverse();
|
||||
sorted.forEach(c => {
|
||||
const msg = c.commit.message;
|
||||
const sha = c.sha.substring(0, 7);
|
||||
const authorDate = new Date(c.commit.author.date);
|
||||
const time = authorDate.toLocaleTimeString('tr-TR', { timeZone: 'Europe/Istanbul', hour: '2-digit', minute: '2-digit' });
|
||||
commitHtml += `<li><strong>[${time}]</strong> (<em>${sha}</em>) ${msg}</li>`;
|
||||
});
|
||||
commitHtml += "</ul>";
|
||||
|
||||
const currentHtml = quill.root.innerHTML.trim();
|
||||
const headerHtml = "<p><strong>Git Commit Mesajları:</strong></p>";
|
||||
|
||||
let newHtml = "";
|
||||
if (currentHtml !== "" && currentHtml !== "<p><br></p>") {
|
||||
newHtml = currentHtml + "<br><br>" + headerHtml + commitHtml;
|
||||
} else {
|
||||
newHtml = headerHtml + commitHtml;
|
||||
}
|
||||
|
||||
quill.setContents([]);
|
||||
quill.clipboard.dangerouslyPasteHTML(newHtml);
|
||||
}
|
||||
|
||||
function hideFallbackCommits() {
|
||||
const container = document.getElementById('git-commits-fallback-container');
|
||||
if (container) {
|
||||
@@ -1160,29 +1341,35 @@
|
||||
return;
|
||||
}
|
||||
|
||||
let commitHtml = "<ul>";
|
||||
const sorted = [...dayCommits].reverse();
|
||||
sorted.forEach(c => {
|
||||
const msg = c.commit.message;
|
||||
const sha = c.sha.substring(0, 7);
|
||||
const authorDate = new Date(c.commit.author.date);
|
||||
const time = authorDate.toLocaleTimeString('tr-TR', { timeZone: 'Europe/Istanbul', hour: '2-digit', minute: '2-digit' });
|
||||
commitHtml += `<li><strong>[${time}]</strong> (<em>${sha}</em>) ${msg}</li>`;
|
||||
// Store these commits in memory for processing
|
||||
activeCommitsToProcess = dayCommits;
|
||||
activeCommitsDateVal = dateVal;
|
||||
|
||||
// Get unique authors
|
||||
const authorsSet = new Set();
|
||||
dayCommits.forEach(item => {
|
||||
const login = item.author ? item.author.login : null;
|
||||
const name = item.commit.author.name;
|
||||
if (login) authorsSet.add(login);
|
||||
if (name) authorsSet.add(name);
|
||||
});
|
||||
commitHtml += "</ul>";
|
||||
const uniqueAuthors = Array.from(authorsSet);
|
||||
|
||||
const currentHtml = quill.root.innerHTML.trim();
|
||||
const headerHtml = "<p><strong>Git Commit Mesajları:</strong></p>";
|
||||
|
||||
let newHtml = "";
|
||||
if (currentHtml !== "" && currentHtml !== "<p><br></p>") {
|
||||
newHtml = currentHtml + "<br><br>" + headerHtml + commitHtml;
|
||||
// If there are multiple authors
|
||||
if (uniqueAuthors.length > 1) {
|
||||
// If we have a saved githubUsername that matches one of the unique authors
|
||||
const matchedAuthor = uniqueAuthors.find(a => githubUsername && a.toLowerCase() === githubUsername.toLowerCase());
|
||||
if (matchedAuthor) {
|
||||
// Automatically filter and insert without modal
|
||||
insertCommitsForAuthor(matchedAuthor);
|
||||
} else {
|
||||
// Show modal to choose
|
||||
openAuthorModal(uniqueAuthors);
|
||||
}
|
||||
} else {
|
||||
newHtml = headerHtml + commitHtml;
|
||||
// Only 1 author, insert directly
|
||||
renderCommitsToEditor(dayCommits);
|
||||
}
|
||||
|
||||
quill.setContents([]);
|
||||
quill.clipboard.dangerouslyPasteHTML(newHtml);
|
||||
})
|
||||
.catch(err => {
|
||||
alert("Commitler çekilirken hata oluştu: " + err.message);
|
||||
|
||||
Reference in New Issue
Block a user