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([
|
$request->validate([
|
||||||
'github_repo' => 'required|string|url|max:255',
|
'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.required' => 'Lütfen Github depo URL\'sini girin.',
|
||||||
'github_repo.url' => 'Geçerli bir URL girilmelidir.',
|
'github_repo.url' => 'Geçerli bir URL girilmelidir.',
|
||||||
'github_repo.max' => 'Github depo URL\'si en fazla 255 karakter olabilir.',
|
'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'));
|
$intern = CareerApplication::findOrFail(session('intern_id'));
|
||||||
|
|
||||||
// Sanitize & format github URL
|
// Sanitize & format github URL
|
||||||
$repoUrl = trim($request->github_repo);
|
$repoUrl = trim($request->github_repo);
|
||||||
|
$username = trim($request->github_username);
|
||||||
|
|
||||||
$intern->update([
|
$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()
|
public function downloadMarkdown()
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class CareerApplication extends Model
|
|||||||
'internship_end_date',
|
'internship_end_date',
|
||||||
'internship_total_days',
|
'internship_total_days',
|
||||||
'github_repo',
|
'github_repo',
|
||||||
|
'github_username',
|
||||||
'certificate_code',
|
'certificate_code',
|
||||||
'transcript_markdown',
|
'transcript_markdown',
|
||||||
'notebook_supervisor_signed',
|
'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">
|
<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">
|
<form action="{{ route('intern.save-github') }}" method="POST" class="space-y-4">
|
||||||
@csrf
|
@csrf
|
||||||
<div>
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<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>
|
||||||
<div class="flex flex-col sm:flex-row gap-3">
|
<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" />
|
<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>
|
</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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -822,6 +830,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</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')
|
@push('styles')
|
||||||
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet" />
|
<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>
|
<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);
|
const githubRepoUrl = @json($intern->github_repo);
|
||||||
|
let githubUsername = @json($intern->github_username);
|
||||||
let activeDayIdx = 0;
|
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() {
|
function hideFallbackCommits() {
|
||||||
const container = document.getElementById('git-commits-fallback-container');
|
const container = document.getElementById('git-commits-fallback-container');
|
||||||
if (container) {
|
if (container) {
|
||||||
@@ -1160,29 +1341,35 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let commitHtml = "<ul>";
|
// Store these commits in memory for processing
|
||||||
const sorted = [...dayCommits].reverse();
|
activeCommitsToProcess = dayCommits;
|
||||||
sorted.forEach(c => {
|
activeCommitsDateVal = dateVal;
|
||||||
const msg = c.commit.message;
|
|
||||||
const sha = c.sha.substring(0, 7);
|
// Get unique authors
|
||||||
const authorDate = new Date(c.commit.author.date);
|
const authorsSet = new Set();
|
||||||
const time = authorDate.toLocaleTimeString('tr-TR', { timeZone: 'Europe/Istanbul', hour: '2-digit', minute: '2-digit' });
|
dayCommits.forEach(item => {
|
||||||
commitHtml += `<li><strong>[${time}]</strong> (<em>${sha}</em>) ${msg}</li>`;
|
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();
|
// If there are multiple authors
|
||||||
const headerHtml = "<p><strong>Git Commit Mesajları:</strong></p>";
|
if (uniqueAuthors.length > 1) {
|
||||||
|
// If we have a saved githubUsername that matches one of the unique authors
|
||||||
let newHtml = "";
|
const matchedAuthor = uniqueAuthors.find(a => githubUsername && a.toLowerCase() === githubUsername.toLowerCase());
|
||||||
if (currentHtml !== "" && currentHtml !== "<p><br></p>") {
|
if (matchedAuthor) {
|
||||||
newHtml = currentHtml + "<br><br>" + headerHtml + commitHtml;
|
// Automatically filter and insert without modal
|
||||||
|
insertCommitsForAuthor(matchedAuthor);
|
||||||
|
} else {
|
||||||
|
// Show modal to choose
|
||||||
|
openAuthorModal(uniqueAuthors);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
newHtml = headerHtml + commitHtml;
|
// Only 1 author, insert directly
|
||||||
|
renderCommitsToEditor(dayCommits);
|
||||||
}
|
}
|
||||||
|
|
||||||
quill.setContents([]);
|
|
||||||
quill.clipboard.dangerouslyPasteHTML(newHtml);
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
alert("Commitler çekilirken hata oluştu: " + err.message);
|
alert("Commitler çekilirken hata oluştu: " + err.message);
|
||||||
|
|||||||
Reference in New Issue
Block a user