Updated Google Scholar Integration,Updated Admin and HOD Dashboard, Added Google Auth: Login Controller and Routes
This commit is contained in:
@@ -15,7 +15,9 @@ class GoogleController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function redirectToGoogle()
|
public function redirectToGoogle()
|
||||||
{
|
{
|
||||||
return Socialite::driver('google')->redirect();
|
// Prefer to hint Google to show accounts from the somaiya.edu domain.
|
||||||
|
// This is only a UI hint — always validate the domain on the callback.
|
||||||
|
return Socialite::driver('google')->with(['hd' => 'somaiya.edu'])->redirect();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,10 +28,20 @@ class GoogleController extends Controller
|
|||||||
try {
|
try {
|
||||||
$googleUser = Socialite::driver('google')->user();
|
$googleUser = Socialite::driver('google')->user();
|
||||||
|
|
||||||
|
// Validate that the user belongs to the somaiya.edu domain.
|
||||||
|
// Google may return a 'hd' (hosted domain) claim; fall back to parsing the email.
|
||||||
|
$email = $googleUser->getEmail();
|
||||||
|
$hostedDomain = data_get($googleUser->user, 'hd');
|
||||||
|
$domain = $hostedDomain ?: (strpos($email, '@') !== false ? substr(strrchr($email, "@"), 1) : null);
|
||||||
|
|
||||||
|
if ($domain !== 'somaiya.edu') {
|
||||||
|
return redirect()->route('login')->withErrors(['error' => 'Please sign in using a somaiya.edu account.']);
|
||||||
|
}
|
||||||
|
|
||||||
$user = User::firstOrCreate(
|
$user = User::firstOrCreate(
|
||||||
['email' => $googleUser->getEmail()],
|
['email' => $email],
|
||||||
[
|
[
|
||||||
'name' => $googleUser->getName(),
|
'name' => $googleUser->getName() ?: $email,
|
||||||
'password' => bcrypt(Str::random(16)), // Generate a random password
|
'password' => bcrypt(Str::random(16)), // Generate a random password
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,57 +2,392 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Yajra\DataTables\Facades\DataTables;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use App\Models\ActivitiesAttended;
|
use App\Models\ActivitiesAttended;
|
||||||
|
use App\Models\ActivitiesOrganised;
|
||||||
|
use App\Models\BooksPublished;
|
||||||
use App\Models\Department;
|
use App\Models\Department;
|
||||||
|
use App\Models\ExternalEngagement;
|
||||||
|
use App\Models\IvOrganised;
|
||||||
|
use App\Models\OnlineCourse;
|
||||||
|
use App\Models\Patent;
|
||||||
|
use App\Models\Publication;
|
||||||
|
use App\Services\ProofDownloadService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
// use Yajra\DataTables\Facades\DataTables;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
class CoordinatorController extends Controller
|
class CoordinatorController extends Controller
|
||||||
{
|
{
|
||||||
// Coordinator dashboard (optional)
|
// Coordinator dashboard (optional)
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
return view('coordinator.dashboard');
|
$user = Auth::user();
|
||||||
|
$departments = Department::where('id', $user->department_id)->get();
|
||||||
|
|
||||||
|
// Collect distinct years from all relevant date fields
|
||||||
|
$years = collect([
|
||||||
|
ActivitiesAttended::where('department_id', $user->department_id) ->selectRaw('YEAR(end_date) as year')->distinct()->pluck('year'),
|
||||||
|
|
||||||
|
ActivitiesOrganised::where('department_id', $user->department_id) ->selectRaw('YEAR(end_date) as year')->distinct()->pluck('year'),
|
||||||
|
|
||||||
|
BooksPublished::where('department_id', $user->department_id) ->selectRaw('YEAR(date_of_publication) as year')->distinct()->pluck('year'),
|
||||||
|
|
||||||
|
ExternalEngagement::where('department_id', $user->department_id) ->selectRaw('YEAR(end_date) as year')->distinct()->pluck('year'),
|
||||||
|
|
||||||
|
IvOrganised::where('department_id', $user->department_id) ->selectRaw('YEAR(end_date) as year')->distinct()->pluck('year'),
|
||||||
|
|
||||||
|
OnlineCourse::where('department_id', $user->department_id) ->selectRaw('YEAR(end_date) as year')->distinct()->pluck('year'),
|
||||||
|
|
||||||
|
Patent::where('department_id', $user->department_id) ->selectRaw('YEAR(date_of_submission) as year')->distinct()->pluck('year'),
|
||||||
|
|
||||||
|
Publication::where('department_id', $user->department_id) ->selectRaw('YEAR(end_date) as year')->distinct()->pluck('year'),
|
||||||
|
|
||||||
|
])->flatten()->filter()->unique()->sortDesc()->values();
|
||||||
|
|
||||||
|
return view('coordinator.dashboard', compact('departments', 'years'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// View responses submitted by users
|
// View responses submitted by users
|
||||||
public function viewActivitiesAttendedResponses()
|
public function viewActivitiesAttendedResponses()
|
||||||
{
|
{
|
||||||
$departments = Department::all();
|
$user = Auth::user();
|
||||||
|
$departments = Department::where('id', $user->department_id)->get();
|
||||||
return view('pages.activities-attended.index', compact('departments'));
|
return view('pages.activities-attended.index', compact('departments'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function viewActivitiesOrganisedResponses()
|
public function viewActivitiesOrganisedResponses()
|
||||||
{
|
{
|
||||||
return view('pages.activities-organised.index');
|
$user = Auth::user();
|
||||||
|
$departments = Department::where('id', $user->department_id)->get();
|
||||||
|
return view('pages.activities-organised.index', compact('departments'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function viewIvOrganisedResponses()
|
public function viewIvOrganisedResponses()
|
||||||
{
|
{
|
||||||
return view('pages.iv-organised.index');
|
$user = Auth::user();
|
||||||
|
$departments = Department::where('id', $user->department_id)->get();
|
||||||
|
return view('pages.iv-organised.index', compact('departments'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function viewPublicationsResponses()
|
public function viewPublicationsResponses()
|
||||||
{
|
{
|
||||||
return view('pages.publications.index');
|
$user = Auth::user();
|
||||||
|
$departments = Department::where('id', $user->department_id)->get();
|
||||||
|
return view('pages.publications.index', compact('departments'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function viewBooksPublishedResponses()
|
public function viewBooksPublishedResponses()
|
||||||
{
|
{
|
||||||
return view('pages.booksPublished.index');
|
$user = Auth::user();
|
||||||
|
$departments = Department::where('id', $user->department_id)->get();
|
||||||
|
return view('pages.booksPublished.index', compact('departments'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function viewExternalEngagementResponses()
|
public function viewExternalEngagementResponses()
|
||||||
{
|
{
|
||||||
return view('pages.externalEngagement.index');
|
$user = Auth::user();
|
||||||
|
$departments = Department::where('id', $user->department_id)->get();
|
||||||
|
return view('pages.externalEngagement.index', compact('departments'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function viewOnlineCoursesResponses()
|
public function viewOnlineCoursesResponses()
|
||||||
{
|
{
|
||||||
return view('pages.onlineCourses.index');
|
$user = Auth::user();
|
||||||
|
$departments = Department::where('id', $user->department_id)->get();
|
||||||
|
return view('pages.onlineCourses.index', compact('departments'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function viewPatentsResponses()
|
public function viewPatentsResponses()
|
||||||
{
|
{
|
||||||
return view('pages.patents.index');
|
$user = Auth::user();
|
||||||
|
$departments = Department::where('id', $user->department_id)->get();
|
||||||
|
return view('pages.patents.index', compact('departments'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function downloadProofs(Request $request, ProofDownloadService $proofDownloadService)
|
||||||
|
{
|
||||||
|
// Validate the request
|
||||||
|
$request->validate([
|
||||||
|
'ids' => 'required|string',
|
||||||
|
'model' => 'sometimes|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$ids = json_decode($request->input('ids'));
|
||||||
|
$modelName = $request->input('model', 'ActivitiesAttended');
|
||||||
|
|
||||||
|
// Model mapping
|
||||||
|
$modelMap = [
|
||||||
|
'ActivitiesAttended' => ActivitiesAttended::class,
|
||||||
|
'ActivitiesOrganised' => ActivitiesOrganised::class,
|
||||||
|
'IvOrganised' => IvOrganised::class,
|
||||||
|
'Publication' => Publication::class,
|
||||||
|
'BooksPublished' => BooksPublished::class,
|
||||||
|
'ExternalEngagement' => ExternalEngagement::class,
|
||||||
|
'OnlineCourse' => OnlineCourse::class,
|
||||||
|
'Patent' => Patent::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Get the model class from the map or default to ActivitiesAttended
|
||||||
|
$modelClass = $modelMap[$modelName] ?? ActivitiesAttended::class;
|
||||||
|
|
||||||
|
$result = $proofDownloadService->downloadProofs($modelClass, $ids);
|
||||||
|
|
||||||
|
if (isset($result['error'])) {
|
||||||
|
return back()->with('error', $result['error']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the zip file as a download
|
||||||
|
return response()->download($result['filePath'], $result['fileName'])->deleteFileAfterSend(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function analyticsActivitiesAttended()
|
||||||
|
{
|
||||||
|
$data = ActivitiesAttended::selectRaw('departments.name as department, COUNT(*) as count')
|
||||||
|
->join('departments', 'activities_attendeds.department_id', '=', 'departments.id')
|
||||||
|
->groupBy('departments.name')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$total = $data->sum('count');
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'labels' => $data->pluck('department'),
|
||||||
|
'values' => $data->pluck('count'),
|
||||||
|
'total' => $total,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function analyticsActivitiesOrganised()
|
||||||
|
{
|
||||||
|
$data = ActivitiesOrganised::selectRaw('departments.name as department, COUNT(*) as count')
|
||||||
|
->join('departments', 'activities_organiseds.department_id', '=', 'departments.id')
|
||||||
|
->groupBy('departments.name')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$total = $data->sum('count');
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'labels' => $data->pluck('department'),
|
||||||
|
'values' => $data->pluck('count'),
|
||||||
|
'total' => $total,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function analyticsIvOrganised()
|
||||||
|
{
|
||||||
|
$data = IvOrganised::selectRaw('departments.name as department, COUNT(*) as count')
|
||||||
|
->join('departments', 'iv_organiseds.department_id', '=', 'departments.id')
|
||||||
|
->groupBy('departments.name')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$total = $data->sum('count');
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'labels' => $data->pluck('department'),
|
||||||
|
'values' => $data->pluck('count'),
|
||||||
|
'total' => $total,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function analyticsPaperPublished()
|
||||||
|
{
|
||||||
|
$data = Publication::selectRaw('departments.name as department, COUNT(*) as count')
|
||||||
|
->join('departments', 'publications.department_id', '=', 'departments.id')
|
||||||
|
->groupBy('departments.name')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$total = $data->sum('count');
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'labels' => $data->pluck('department'),
|
||||||
|
'values' => $data->pluck('count'),
|
||||||
|
'total' => $total,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function analyticsBooksPublished()
|
||||||
|
{
|
||||||
|
$data = BooksPublished::selectRaw('departments.name as department, COUNT(*) as count')
|
||||||
|
->join('departments', 'books_published.department_id', '=', 'departments.id')
|
||||||
|
->groupBy('departments.name')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$total = $data->sum('count');
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'labels' => $data->pluck('department'),
|
||||||
|
'values' => $data->pluck('count'),
|
||||||
|
'total' => $total,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function analyticsExternalEngagement()
|
||||||
|
{
|
||||||
|
$data = ExternalEngagement::selectRaw('departments.name as department, COUNT(*) as count')
|
||||||
|
->join('departments', 'external_engagements.department_id', '=', 'departments.id')
|
||||||
|
->groupBy('departments.name')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$total = $data->sum('count');
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'labels' => $data->pluck('department'),
|
||||||
|
'values' => $data->pluck('count'),
|
||||||
|
'total' => $total,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function analyticsOnlineCourse()
|
||||||
|
{
|
||||||
|
$data = OnlineCourse::selectRaw('departments.name as department, COUNT(*) as count')
|
||||||
|
->join('departments', 'online_courses.department_id', '=', 'departments.id')
|
||||||
|
->groupBy('departments.name')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$total = $data->sum('count');
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'labels' => $data->pluck('department'),
|
||||||
|
'values' => $data->pluck('count'),
|
||||||
|
'total' => $total,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function analyticsPatent()
|
||||||
|
{
|
||||||
|
$data = Patent::selectRaw('departments.name as department, COUNT(*) as count')
|
||||||
|
->join('departments', 'patents.department_id', '=', 'departments.id')
|
||||||
|
->groupBy('departments.name')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$total = $data->sum('count');
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'labels' => $data->pluck('department'),
|
||||||
|
'values' => $data->pluck('count'),
|
||||||
|
'total' => $total,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function analyticsComparison(Request $request)
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
$departmentId = $request->query('department_id');
|
||||||
|
if (empty($departmentId) && $user && $user->role_id == 2) {
|
||||||
|
$departmentId = $user->department_id;
|
||||||
|
}
|
||||||
|
$year = $request->query('year');
|
||||||
|
|
||||||
|
$models = [
|
||||||
|
'ActivitiesAttended' => ['class' => ActivitiesAttended::class, 'date_field' => 'end_date'],
|
||||||
|
'ActivitiesOrganised' => ['class' => ActivitiesOrganised::class, 'date_field' => 'end_date'],
|
||||||
|
'BooksPublished' => ['class' => BooksPublished::class, 'date_field' => 'date_of_publication'],
|
||||||
|
'ExternalEngagement' => ['class' => ExternalEngagement::class, 'date_field' => 'end_date'],
|
||||||
|
'IvOrganised' => ['class' => IvOrganised::class, 'date_field' => 'end_date'],
|
||||||
|
'OnlineCourse' => ['class' => OnlineCourse::class, 'date_field' => 'end_date'],
|
||||||
|
'Patent' => ['class' => Patent::class, 'date_field' => 'date_of_submission'],
|
||||||
|
'Publication' => ['class' => Publication::class, 'date_field' => 'end_date'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
foreach ($models as $label => $modelConfig) {
|
||||||
|
$query = $modelConfig['class']::query();
|
||||||
|
if($departmentId) {
|
||||||
|
$query->where('department_id', $departmentId);
|
||||||
|
}
|
||||||
|
if ($year && $year !== 'all') {
|
||||||
|
$query->whereYear($modelConfig['date_field'], $year);
|
||||||
|
}
|
||||||
|
$count = $query->count();
|
||||||
|
if ($count > 0) { // Only include data with non-zero count
|
||||||
|
$data[] = [
|
||||||
|
'label' => $label,
|
||||||
|
'count' => $count,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = array_sum(array_column($data, 'count'));
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'labels' => array_column($data, 'label'),
|
||||||
|
'values' => array_column($data, 'count'),
|
||||||
|
'total' => $total
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function analyticsContribution(Request $request)
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
$model = $request->query('model', 'all');
|
||||||
|
$year = $request->query('year');
|
||||||
|
$departmentId = $request->query('department_id');
|
||||||
|
if (empty($departmentId) && $user && $user->role_id == 2) {
|
||||||
|
$departmentId = $user->department_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$models = [
|
||||||
|
'ActivitiesAttended' => ['class' => ActivitiesAttended::class, 'date_field' => 'end_date'],
|
||||||
|
'ActivitiesOrganised' => ['class' => ActivitiesOrganised::class, 'date_field' => 'end_date'],
|
||||||
|
'BooksPublished' => ['class' => BooksPublished::class, 'date_field' => 'date_of_publication'],
|
||||||
|
'ExternalEngagement' => ['class' => ExternalEngagement::class, 'date_field' => 'end_date'],
|
||||||
|
'IvOrganised' => ['class' => IvOrganised::class, 'date_field' => 'end_date'],
|
||||||
|
'OnlineCourse' => ['class' => OnlineCourse::class, 'date_field' => 'end_date'],
|
||||||
|
'Patent' => ['class' => Patent::class, 'date_field' => 'date_of_submission'],
|
||||||
|
'Publication' => ['class' => Publication::class, 'date_field' => 'end_date'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
|
||||||
|
if ($model === 'all') {
|
||||||
|
$departments = Department::when($departmentId, function ($query) use ($departmentId) {
|
||||||
|
return $query->where('id', $departmentId);
|
||||||
|
})->get();
|
||||||
|
|
||||||
|
foreach ($departments as $department) {
|
||||||
|
$count = 0;
|
||||||
|
foreach ($models as $modelConfig) {
|
||||||
|
$query = $modelConfig['class']::where('department_id', $department->id);
|
||||||
|
if ($year && $year !== 'all') {
|
||||||
|
$query->whereYear($modelConfig['date_field'], $year);
|
||||||
|
}
|
||||||
|
$count += $query->count();
|
||||||
|
}
|
||||||
|
if ($count > 0) { // Only include data with non-zero count
|
||||||
|
$data[] = [
|
||||||
|
'label' => $department->name,
|
||||||
|
'count' => $count,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$modelConfig = $models[$model];
|
||||||
|
$modelClass = $modelConfig['class'];
|
||||||
|
$table = (new $modelClass)->getTable();
|
||||||
|
|
||||||
|
$query = $modelClass::selectRaw('departments.name as label, COUNT(*) as count')
|
||||||
|
->join('departments', $table . '.department_id', '=', 'departments.id')
|
||||||
|
->groupBy('departments.name');
|
||||||
|
|
||||||
|
if ($year && $year !== 'all') {
|
||||||
|
$query->whereYear($table . '.' . $modelConfig['date_field'], $year);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($departmentId) {
|
||||||
|
$query->where($table . '.department_id', $departmentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $query->get()->filter(function ($item) {
|
||||||
|
return $item['count'] > 0; // Filter out data with zero count
|
||||||
|
})->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = array_sum(array_column($data, 'count'));
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'labels' => array_column($data, 'label'),
|
||||||
|
'values' => array_column($data, 'count'),
|
||||||
|
'total' => $total
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,25 +201,39 @@ class PublicationsController extends Controller
|
|||||||
|
|
||||||
if (!$user || !$user->scholar_url) {
|
if (!$user || !$user->scholar_url) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => false,
|
'error' => 'No Google Scholar URL found for the logged-in user.'
|
||||||
'message' => 'No Google Scholar URL found for the logged-in user.'
|
|
||||||
], 400);
|
], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
$scholarService = new \App\Services\ScholarService();
|
$scholarService = new \App\Services\ScholarService();
|
||||||
$publications = $scholarService->fetchPublications($user->scholar_url);
|
$result = $scholarService->fetchPublications($user->scholar_url);
|
||||||
|
|
||||||
|
// If the service returned an error payload, forward it to UI
|
||||||
|
if (is_array($result) && array_key_exists('error', $result)) {
|
||||||
|
// Log more details if available
|
||||||
|
if (isset($result['body'])) {
|
||||||
|
\Log::error('ScholarService error body', ['body' => $result['body']]);
|
||||||
|
} elseif (isset($result['raw'])) {
|
||||||
|
\Log::warning('ScholarService returned empty articles', ['raw' => $result['raw']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'error' => $result['error']
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expecting successful shape ['data' => [...]]
|
||||||
|
$publications = $result['data'] ?? [];
|
||||||
|
|
||||||
if (empty($publications)) {
|
if (empty($publications)) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => false,
|
'error' => 'No publications found from the provided Scholar profile.'
|
||||||
'message' => 'No publications found from the provided Scholar profile.'
|
], 200);
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
|
||||||
'count' => count($publications),
|
|
||||||
'data' => $publications,
|
'data' => $publications,
|
||||||
|
'count' => count($publications),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class UserController extends Controller
|
|||||||
->addColumn('role_name', function ($response) {
|
->addColumn('role_name', function ($response) {
|
||||||
$roles = [
|
$roles = [
|
||||||
1 => 'Admin',
|
1 => 'Admin',
|
||||||
2 => 'Coordinator',
|
2 => 'HOD',
|
||||||
3 => 'Faculty',
|
3 => 'Faculty',
|
||||||
];
|
];
|
||||||
$options = '';
|
$options = '';
|
||||||
|
|||||||
@@ -10,20 +10,22 @@ class ScholarService
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// Extract Google Scholar profile ID (e.g., ssU-uBEAAAAJ)
|
// Extract Google Scholar profile ID (e.g., ssU-uBEAAAAJ)
|
||||||
preg_match('/user=([^&]+)/', $url, $match);
|
preg_match('/[?&]user=([^&]+)/', $url, $match);
|
||||||
$profileId = $match[1] ?? null;
|
$profileId = $match[1] ?? null;
|
||||||
|
|
||||||
if (!$profileId) {
|
if (!$profileId) {
|
||||||
\Log::warning('No Google Scholar ID found in URL: ' . $url);
|
$msg = 'No Google Scholar ID found in URL: ' . $url;
|
||||||
return [];
|
\Log::warning($msg);
|
||||||
|
return ['error' => $msg];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load API key from .env
|
// Load API key from .env
|
||||||
$apiKey = env('SERPAPI_KEY');
|
$apiKey = env('SERPAPI_KEY');
|
||||||
|
|
||||||
if (!$apiKey) {
|
if (!$apiKey) {
|
||||||
\Log::error('SERPAPI_KEY not found in environment variables');
|
$msg = 'SERPAPI_KEY not found in environment variables';
|
||||||
return [];
|
\Log::error($msg);
|
||||||
|
return ['error' => $msg];
|
||||||
}
|
}
|
||||||
|
|
||||||
$endpoint = 'https://serpapi.com/search.json';
|
$endpoint = 'https://serpapi.com/search.json';
|
||||||
@@ -34,8 +36,10 @@ class ScholarService
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (!$response->successful()) {
|
if (!$response->successful()) {
|
||||||
\Log::error('Scholar API request failed: ' . $response->status());
|
$status = $response->status();
|
||||||
return [];
|
$body = $response->body();
|
||||||
|
\Log::error('Scholar API request failed', ['status' => $status, 'body' => $body]);
|
||||||
|
return ['error' => 'Scholar API request failed: ' . $status, 'body' => $body];
|
||||||
}
|
}
|
||||||
|
|
||||||
$json = $response->json();
|
$json = $response->json();
|
||||||
@@ -46,8 +50,13 @@ class ScholarService
|
|||||||
?? $json['results']
|
?? $json['results']
|
||||||
?? [];
|
?? [];
|
||||||
|
|
||||||
|
if (empty($articles)) {
|
||||||
|
\Log::warning('Scholar API returned no articles', ['profileId' => $profileId, 'response' => $json]);
|
||||||
|
return ['error' => 'No publications returned by Scholar API', 'raw' => $json];
|
||||||
|
}
|
||||||
|
|
||||||
// ✅ Format and return
|
// ✅ Format and return
|
||||||
return collect($articles)
|
$formatted = collect($articles)
|
||||||
->map(function ($a) {
|
->map(function ($a) {
|
||||||
return [
|
return [
|
||||||
'title' => $a['title'] ?? $a['name'] ?? null,
|
'title' => $a['title'] ?? $a['name'] ?? null,
|
||||||
@@ -59,6 +68,8 @@ class ScholarService
|
|||||||
})
|
})
|
||||||
->toArray();
|
->toArray();
|
||||||
|
|
||||||
|
return ['data' => $formatted];
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
\Log::error('ScholarService API error: ' . $e->getMessage());
|
\Log::error('ScholarService API error: ' . $e->getMessage());
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -20,7 +20,8 @@
|
|||||||
"laravel/sail": "^1.26",
|
"laravel/sail": "^1.26",
|
||||||
"mockery/mockery": "^1.6",
|
"mockery/mockery": "^1.6",
|
||||||
"nunomaduro/collision": "^8.1",
|
"nunomaduro/collision": "^8.1",
|
||||||
"phpunit/phpunit": "^11.0.1"
|
"phpunit/phpunit": "^11.0.1",
|
||||||
|
"symfony/panther": "^2.2"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|||||||
714
composer.lock
generated
714
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "d9ba03cac29ec7ec3e39de5902752aa9",
|
"content-hash": "907f80ba2043dbb55bff6a61836bb507",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
@@ -7755,6 +7755,73 @@
|
|||||||
},
|
},
|
||||||
"time": "2025-01-13T16:57:11+00:00"
|
"time": "2025-01-13T16:57:11+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "masterminds/html5",
|
||||||
|
"version": "2.10.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Masterminds/html5-php.git",
|
||||||
|
"reference": "fcf91eb64359852f00d921887b219479b4f21251"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251",
|
||||||
|
"reference": "fcf91eb64359852f00d921887b219479b4f21251",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-dom": "*",
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.7-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Masterminds\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Matt Butcher",
|
||||||
|
"email": "technosophos@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Matt Farina",
|
||||||
|
"email": "matt@mattfarina.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Asmir Mustafic",
|
||||||
|
"email": "goetas@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "An HTML5 parser and serializer.",
|
||||||
|
"homepage": "http://masterminds.github.io/html5-php",
|
||||||
|
"keywords": [
|
||||||
|
"HTML5",
|
||||||
|
"dom",
|
||||||
|
"html",
|
||||||
|
"parser",
|
||||||
|
"querypath",
|
||||||
|
"serializer",
|
||||||
|
"xml"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Masterminds/html5-php/issues",
|
||||||
|
"source": "https://github.com/Masterminds/html5-php/tree/2.10.0"
|
||||||
|
},
|
||||||
|
"time": "2025-07-25T09:04:22+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "mockery/mockery",
|
"name": "mockery/mockery",
|
||||||
"version": "1.6.12",
|
"version": "1.6.12",
|
||||||
@@ -8114,6 +8181,72 @@
|
|||||||
},
|
},
|
||||||
"time": "2022-02-21T01:04:05+00:00"
|
"time": "2022-02-21T01:04:05+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "php-webdriver/webdriver",
|
||||||
|
"version": "1.15.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-webdriver/php-webdriver.git",
|
||||||
|
"reference": "998e499b786805568deaf8cbf06f4044f05d91bf"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/998e499b786805568deaf8cbf06f4044f05d91bf",
|
||||||
|
"reference": "998e499b786805568deaf8cbf06f4044f05d91bf",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-zip": "*",
|
||||||
|
"php": "^7.3 || ^8.0",
|
||||||
|
"symfony/polyfill-mbstring": "^1.12",
|
||||||
|
"symfony/process": "^5.0 || ^6.0 || ^7.0"
|
||||||
|
},
|
||||||
|
"replace": {
|
||||||
|
"facebook/webdriver": "*"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"ergebnis/composer-normalize": "^2.20.0",
|
||||||
|
"ondram/ci-detector": "^4.0",
|
||||||
|
"php-coveralls/php-coveralls": "^2.4",
|
||||||
|
"php-mock/php-mock-phpunit": "^2.0",
|
||||||
|
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||||
|
"phpunit/phpunit": "^9.3",
|
||||||
|
"squizlabs/php_codesniffer": "^3.5",
|
||||||
|
"symfony/var-dumper": "^5.0 || ^6.0 || ^7.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-SimpleXML": "For Firefox profile creation"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"lib/Exception/TimeoutException.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Facebook\\WebDriver\\": "lib/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.",
|
||||||
|
"homepage": "https://github.com/php-webdriver/php-webdriver",
|
||||||
|
"keywords": [
|
||||||
|
"Chromedriver",
|
||||||
|
"geckodriver",
|
||||||
|
"php",
|
||||||
|
"selenium",
|
||||||
|
"webdriver"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/php-webdriver/php-webdriver/issues",
|
||||||
|
"source": "https://github.com/php-webdriver/php-webdriver/tree/1.15.2"
|
||||||
|
},
|
||||||
|
"time": "2024-11-21T15:12:59+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
"version": "11.0.8",
|
"version": "11.0.8",
|
||||||
@@ -9516,6 +9649,581 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-10-20T05:08:20+00:00"
|
"time": "2024-10-20T05:08:20+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/browser-kit",
|
||||||
|
"version": "v7.3.6",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/browser-kit.git",
|
||||||
|
"reference": "e9a9fd604296b17bf90939c3647069f1f16ef04e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/e9a9fd604296b17bf90939c3647069f1f16ef04e",
|
||||||
|
"reference": "e9a9fd604296b17bf90939c3647069f1f16ef04e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/dom-crawler": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/css-selector": "^6.4|^7.0",
|
||||||
|
"symfony/http-client": "^6.4|^7.0",
|
||||||
|
"symfony/mime": "^6.4|^7.0",
|
||||||
|
"symfony/process": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\BrowserKit\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/browser-kit/tree/v7.3.6"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-11-05T07:57:47+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/dependency-injection",
|
||||||
|
"version": "v7.3.6",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/dependency-injection.git",
|
||||||
|
"reference": "98af8bb46c56aedd9dd5a7f0414fc72bf2dcfe69"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/98af8bb46c56aedd9dd5a7f0414fc72bf2dcfe69",
|
||||||
|
"reference": "98af8bb46c56aedd9dd5a7f0414fc72bf2dcfe69",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"psr/container": "^1.1|^2.0",
|
||||||
|
"symfony/deprecation-contracts": "^2.5|^3",
|
||||||
|
"symfony/service-contracts": "^3.5",
|
||||||
|
"symfony/var-exporter": "^6.4.20|^7.2.5"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"ext-psr": "<1.1|>=2",
|
||||||
|
"symfony/config": "<6.4",
|
||||||
|
"symfony/finder": "<6.4",
|
||||||
|
"symfony/yaml": "<6.4"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/container-implementation": "1.1|2.0",
|
||||||
|
"symfony/service-implementation": "1.1|2.0|3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/config": "^6.4|^7.0",
|
||||||
|
"symfony/expression-language": "^6.4|^7.0",
|
||||||
|
"symfony/yaml": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\DependencyInjection\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/dependency-injection/tree/v7.3.6"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-10-31T10:11:11+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/dom-crawler",
|
||||||
|
"version": "v7.3.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/dom-crawler.git",
|
||||||
|
"reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/efa076ea0eeff504383ff0dcf827ea5ce15690ba",
|
||||||
|
"reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"masterminds/html5": "^2.6",
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/polyfill-ctype": "~1.8",
|
||||||
|
"symfony/polyfill-mbstring": "~1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/css-selector": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\DomCrawler\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Eases DOM navigation for HTML and XML documents",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/dom-crawler/tree/v7.3.3"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-08-06T20:13:54+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/http-client",
|
||||||
|
"version": "v7.3.6",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/http-client.git",
|
||||||
|
"reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/http-client/zipball/3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de",
|
||||||
|
"reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"psr/log": "^1|^2|^3",
|
||||||
|
"symfony/deprecation-contracts": "^2.5|^3",
|
||||||
|
"symfony/http-client-contracts": "~3.4.4|^3.5.2",
|
||||||
|
"symfony/polyfill-php83": "^1.29",
|
||||||
|
"symfony/service-contracts": "^2.5|^3"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"amphp/amp": "<2.5",
|
||||||
|
"amphp/socket": "<1.1",
|
||||||
|
"php-http/discovery": "<1.15",
|
||||||
|
"symfony/http-foundation": "<6.4"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"php-http/async-client-implementation": "*",
|
||||||
|
"php-http/client-implementation": "*",
|
||||||
|
"psr/http-client-implementation": "1.0",
|
||||||
|
"symfony/http-client-implementation": "3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"amphp/http-client": "^4.2.1|^5.0",
|
||||||
|
"amphp/http-tunnel": "^1.0|^2.0",
|
||||||
|
"guzzlehttp/promises": "^1.4|^2.0",
|
||||||
|
"nyholm/psr7": "^1.0",
|
||||||
|
"php-http/httplug": "^1.0|^2.0",
|
||||||
|
"psr/http-client": "^1.0",
|
||||||
|
"symfony/amphp-http-client-meta": "^1.0|^2.0",
|
||||||
|
"symfony/dependency-injection": "^6.4|^7.0",
|
||||||
|
"symfony/http-kernel": "^6.4|^7.0",
|
||||||
|
"symfony/messenger": "^6.4|^7.0",
|
||||||
|
"symfony/process": "^6.4|^7.0",
|
||||||
|
"symfony/rate-limiter": "^6.4|^7.0",
|
||||||
|
"symfony/stopwatch": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\HttpClient\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"http"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/http-client/tree/v7.3.6"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-11-05T17:41:46+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/http-client-contracts",
|
||||||
|
"version": "v3.6.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/http-client-contracts.git",
|
||||||
|
"reference": "75d7043853a42837e68111812f4d964b01e5101c"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c",
|
||||||
|
"reference": "75d7043853a42837e68111812f4d964b01e5101c",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"thanks": {
|
||||||
|
"url": "https://github.com/symfony/contracts",
|
||||||
|
"name": "symfony/contracts"
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "3.6-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Contracts\\HttpClient\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Test/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Generic abstractions related to HTTP clients",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"abstractions",
|
||||||
|
"contracts",
|
||||||
|
"decoupling",
|
||||||
|
"interfaces",
|
||||||
|
"interoperability",
|
||||||
|
"standards"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-04-29T11:18:49+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/panther",
|
||||||
|
"version": "v2.2.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/panther.git",
|
||||||
|
"reference": "b7e0f834c9046918972edb3dde2ecc4a20f6155e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/panther/zipball/b7e0f834c9046918972edb3dde2ecc4a20f6155e",
|
||||||
|
"reference": "b7e0f834c9046918972edb3dde2ecc4a20f6155e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-dom": "*",
|
||||||
|
"ext-libxml": "*",
|
||||||
|
"php": ">=8.0",
|
||||||
|
"php-webdriver/webdriver": "^1.8.2",
|
||||||
|
"symfony/browser-kit": "^5.4 || ^6.4 || ^7.0",
|
||||||
|
"symfony/dependency-injection": "^5.4 || ^6.4 || ^7.0",
|
||||||
|
"symfony/deprecation-contracts": "^2.4 || ^3",
|
||||||
|
"symfony/dom-crawler": "^5.4 || ^6.4 || ^7.0",
|
||||||
|
"symfony/http-client": "^6.4 || ^7.0",
|
||||||
|
"symfony/http-kernel": "^5.4 || ^6.4 || ^7.0",
|
||||||
|
"symfony/process": "^5.4 || ^6.4 || ^7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/css-selector": "^5.4 || ^6.4 || ^7.0",
|
||||||
|
"symfony/framework-bundle": "^5.4 || ^6.4 || ^7.0",
|
||||||
|
"symfony/mime": "^5.4 || ^6.4 || ^7.0",
|
||||||
|
"symfony/phpunit-bridge": "^7.2.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "2.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\Panther\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Kévin Dunglas",
|
||||||
|
"email": "dunglas@gmail.com",
|
||||||
|
"homepage": "https://dunglas.fr"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A browser testing and web scraping library for PHP and Symfony.",
|
||||||
|
"homepage": "https://dunglas.fr",
|
||||||
|
"keywords": [
|
||||||
|
"e2e",
|
||||||
|
"scraping",
|
||||||
|
"selenium",
|
||||||
|
"symfony",
|
||||||
|
"testing",
|
||||||
|
"webdriver"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/symfony/panther/issues",
|
||||||
|
"source": "https://github.com/symfony/panther/tree/v2.2.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://www.panthera.org/donate",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/dunglas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/panther",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-01-30T13:11:55+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/var-exporter",
|
||||||
|
"version": "v7.3.4",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/var-exporter.git",
|
||||||
|
"reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f020b544a30a7fe8ba972e53ee48a74c0bc87f4",
|
||||||
|
"reference": "0f020b544a30a7fe8ba972e53ee48a74c0bc87f4",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/deprecation-contracts": "^2.5|^3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/property-access": "^6.4|^7.0",
|
||||||
|
"symfony/serializer": "^6.4|^7.0",
|
||||||
|
"symfony/var-dumper": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\VarExporter\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Allows exporting any serializable PHP data structure to plain PHP code",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"clone",
|
||||||
|
"construct",
|
||||||
|
"export",
|
||||||
|
"hydrate",
|
||||||
|
"instantiate",
|
||||||
|
"lazy-loading",
|
||||||
|
"proxy",
|
||||||
|
"serialize"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/var-exporter/tree/v7.3.4"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-09-11T10:12:26+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/yaml",
|
"name": "symfony/yaml",
|
||||||
"version": "v7.2.0",
|
"version": "v7.2.0",
|
||||||
@@ -9641,12 +10349,12 @@
|
|||||||
],
|
],
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
"stability-flags": [],
|
"stability-flags": {},
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "^8.2"
|
"php": "^8.2"
|
||||||
},
|
},
|
||||||
"platform-dev": [],
|
"platform-dev": {},
|
||||||
"plugin-api-version": "2.6.0"
|
"plugin-api-version": "2.6.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,223 @@
|
|||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('header')
|
@section('header')
|
||||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||||
Coordinator Dashboard
|
Coordinator Dashboard
|
||||||
</h2>
|
</h2>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
||||||
<div class="p-6 text-gray-900">
|
<div class="p-6 text-gray-900">
|
||||||
Welcome, Coordinator! Manage department-related tasks here.
|
Welcome, Coordinator! Manage department-related tasks here.
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-6">
|
|
||||||
<a href="{{ route('departments.index') }}" class="text-blue-500 underline">View Departments</a><br>
|
|
||||||
<a href="{{ route('users.index') }}" class="text-blue-500 underline">View Faculty</a><br>
|
|
||||||
<a href="{{ route('profile.edit') }}" class="text-blue-500 underline">Edit Profile</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Graphs Section -->
|
||||||
|
<div class="mt-6">
|
||||||
|
<h3 class="text-lg font-semibold">Analytics</h3>
|
||||||
|
<div id="graphs-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-6 mt-4">
|
||||||
|
<!-- First three graphs -->
|
||||||
|
<div class="bg-white p-4 shadow rounded">
|
||||||
|
<h4 class="text-md font-semibold mb-4">Comparison of All Models by Department</h4>
|
||||||
|
<div class="flex flex-row sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<select id="departmentSelector" class="mb-4 px-7 border rounded">
|
||||||
|
@foreach($departments as $department)
|
||||||
|
<option value="{{ $department->id }}">{{ $department->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
<select id="comparisonYearSelector" class="mb-4 px-7 border rounded">
|
||||||
|
<option value="all">All Years</option>
|
||||||
|
@foreach($years as $year)
|
||||||
|
<option value="{{ $year }}">{{ $year }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<canvas id="comparisonChart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white p-4 shadow rounded">
|
||||||
|
<h4 class="text-md font-semibold mb-4">Total Contribution by Department</h4>
|
||||||
|
<div class="flex flex-row sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<select id="modelSelector" class="mb-4 sm:mb-0 p-2 border rounded">
|
||||||
|
<option value="all">All Categories</option>
|
||||||
|
<option value="ActivitiesAttended">Activities Attended</option>
|
||||||
|
<option value="ActivitiesOrganised">Activities Organised</option>
|
||||||
|
<option value="BooksPublished">Books Published</option>
|
||||||
|
<option value="ExternalEngagement">External Engagement</option>
|
||||||
|
<option value="IvOrganised">IV Organised</option>
|
||||||
|
<option value="OnlineCourse">Online Courses</option>
|
||||||
|
<option value="Patent">Patents</option>
|
||||||
|
<option value="Publication">Publications</option>
|
||||||
|
</select>
|
||||||
|
<select id="contributionYearSelector" class="mb-4 sm:ml-4 px-7 border rounded">
|
||||||
|
<option value="all">All Years</option>
|
||||||
|
@foreach($years as $year)
|
||||||
|
<option value="{{ $year }}">{{ $year }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<canvas id="contributionChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
|
||||||
|
@section('scripts')
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const departmentSelector = document.getElementById('departmentSelector');
|
||||||
|
const comparisonYearSelector = document.getElementById('comparisonYearSelector');
|
||||||
|
const modelSelector = document.getElementById('modelSelector');
|
||||||
|
const contributionYearSelector = document.getElementById('contributionYearSelector');
|
||||||
|
|
||||||
|
// Use coordinator route names (must exist in routes/web.php)
|
||||||
|
const comparisonBaseUrl = "{{ route('coordinator.analytics.comparison') }}";
|
||||||
|
const contributionBaseUrl = "{{ route('coordinator.analytics.contribution') }}";
|
||||||
|
|
||||||
|
function fetchComparisonData(departmentId, year) {
|
||||||
|
const yearParam = year === 'all' ? '' : `&year=${year}`;
|
||||||
|
const url = departmentId
|
||||||
|
? `${comparisonBaseUrl}?department_id=${departmentId}${yearParam}`
|
||||||
|
: `${comparisonBaseUrl}?${yearParam}`;
|
||||||
|
|
||||||
|
|
||||||
|
fetch(url)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) throw new Error('HTTP ' + response.status);
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
const ctx = document.getElementById('comparisonChart').getContext('2d');
|
||||||
|
if (window.comparisonChartInstance) window.comparisonChartInstance.destroy();
|
||||||
|
|
||||||
|
const modelPages = [
|
||||||
|
'ActivitiesAttendedResponses',
|
||||||
|
'ActivitiesOrganisedResponses',
|
||||||
|
'BooksPublishedResponses',
|
||||||
|
'ExternalEngagementResponses',
|
||||||
|
'IvOrganisedResponses',
|
||||||
|
'OnlineCoursesResponses',
|
||||||
|
'PatentsResponses',
|
||||||
|
'PublicationsResponses'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Redirect to coordinator pages
|
||||||
|
const barLinks = modelPages.map(page => `/coordinator/${page}`);
|
||||||
|
|
||||||
|
window.comparisonChartInstance = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: data.labels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Comparison by Department',
|
||||||
|
data: data.values,
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
plugins: [ChartDataLabels],
|
||||||
|
options: {
|
||||||
|
plugins: { legend: { display: false } },
|
||||||
|
responsive: true,
|
||||||
|
scales: { y: { beginAtZero: true } },
|
||||||
|
onClick: function(evt, elements) {
|
||||||
|
if (elements.length > 0) {
|
||||||
|
const idx = elements[0].index;
|
||||||
|
const deptId = departmentSelector ? departmentSelector.value : departmentId;
|
||||||
|
if (idx < barLinks.length) window.location.href = `${barLinks[idx]}?department_id=${deptId}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// show total
|
||||||
|
let comparisonTotal = document.getElementById('comparisonTotal');
|
||||||
|
if (!comparisonTotal) {
|
||||||
|
comparisonTotal = document.createElement('p');
|
||||||
|
comparisonTotal.id = 'comparisonTotal';
|
||||||
|
comparisonTotal.classList.add('text-center', 'font-bold', 'mt-2');
|
||||||
|
document.getElementById('comparisonChart').parentElement.appendChild(comparisonTotal);
|
||||||
|
}
|
||||||
|
comparisonTotal.textContent = `Total: ${data.total}`;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Comparison fetch error:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchContributionData(model, year) {
|
||||||
|
const yearParam = year === 'all' ? '' : `&year=${year}`;
|
||||||
|
const url = `${contributionBaseUrl}?model=${model}${yearParam}`;
|
||||||
|
|
||||||
|
fetch(url)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) throw new Error('HTTP ' + response.status);
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
const ctx = document.getElementById('contributionChart').getContext('2d');
|
||||||
|
if (window.contributionChartInstance) window.contributionChartInstance.destroy();
|
||||||
|
|
||||||
|
// map labels to coordinator pages (adjust if needed)
|
||||||
|
const barLinks = data.labels.map((label, idx) => `/coordinator/contribution/${idx+1}`);
|
||||||
|
|
||||||
|
window.contributionChartInstance = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: data.labels,
|
||||||
|
datasets: [{ label: 'Total Contribution by Department', data: data.values }]
|
||||||
|
},
|
||||||
|
plugins: [ChartDataLabels],
|
||||||
|
options: {
|
||||||
|
plugins: { legend: { display: false } },
|
||||||
|
responsive: true,
|
||||||
|
scales: { y: { beginAtZero: true } },
|
||||||
|
onClick: function(evt, elements) {
|
||||||
|
if (elements.length > 0) {
|
||||||
|
const idx = elements[0].index;
|
||||||
|
window.location.href = barLinks[idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let contributionTotal = document.getElementById('contributionTotal');
|
||||||
|
if (!contributionTotal) {
|
||||||
|
contributionTotal = document.createElement('p');
|
||||||
|
contributionTotal.id = 'contributionTotal';
|
||||||
|
contributionTotal.classList.add('text-center', 'font-bold', 'mt-2');
|
||||||
|
document.getElementById('contributionChart').parentElement.appendChild(contributionTotal);
|
||||||
|
}
|
||||||
|
contributionTotal.textContent = `Total: ${data.total}`;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('Contribution fetch error:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// INITIAL FETCH: use the selected department (not hardcoded 1)
|
||||||
|
const initialDept = departmentSelector ? departmentSelector.value : null;
|
||||||
|
const initialYear = comparisonYearSelector ? comparisonYearSelector.value : 'all';
|
||||||
|
if (initialDept) {
|
||||||
|
fetchComparisonData(initialDept, initialYear);
|
||||||
|
} else {
|
||||||
|
// fallback: let server infer (coordinator filter), call without department_id
|
||||||
|
fetchComparisonData('', initialYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
// initial contribution
|
||||||
|
const initModel = modelSelector ? modelSelector.value : 'all';
|
||||||
|
const initContribYear = contributionYearSelector ? contributionYearSelector.value : 'all';
|
||||||
|
fetchContributionData(initModel, initContribYear);
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
if (departmentSelector) departmentSelector.addEventListener('change', () => fetchComparisonData(departmentSelector.value, comparisonYearSelector.value));
|
||||||
|
if (comparisonYearSelector) comparisonYearSelector.addEventListener('change', () => fetchComparisonData(departmentSelector.value, comparisonYearSelector.value));
|
||||||
|
if (modelSelector) modelSelector.addEventListener('change', () => fetchContributionData(modelSelector.value, contributionYearSelector.value));
|
||||||
|
if (contributionYearSelector) contributionYearSelector.addEventListener('change', () => fetchContributionData(modelSelector.value, contributionYearSelector.value));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -299,7 +299,23 @@ Route::prefix('admin')->name('admin.')->group(function () {
|
|||||||
Route::get('analytics/contribution', [AdminController::class, 'analyticsContribution'])->name('analytics.contribution');
|
Route::get('analytics/contribution', [AdminController::class, 'analyticsContribution'])->name('analytics.contribution');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::prefix('coordinator')->name('coordinator.')->group(function () {
|
||||||
|
Route::get('analytics/activitiesAttended', [CoordinatorController::class, 'analyticsActivitiesAttended'])->name('analytics.activitiesAttended');
|
||||||
|
Route::get('analytics/activitiesOrganised', [CoordinatorController::class, 'analyticsActivitiesOrganised'])->name('analytics.activitiesOrganised');
|
||||||
|
Route::get('analytics/ivOrganised', [CoordinatorController::class, 'analyticsIvOrganised'])->name('analytics.ivOrganised');
|
||||||
|
Route::get('analytics/paperPublished', [CoordinatorController::class, 'analyticsPaperPublished'])->name('analytics.paperPublished');
|
||||||
|
Route::get('analytics/booksPublished', [CoordinatorController::class, 'analyticsBooksPublished'])->name('analytics.booksPublished');
|
||||||
|
Route::get('analytics/externalEngagement', [CoordinatorController::class, 'analyticsExternalEngagement'])->name('analytics.externalEngagement');
|
||||||
|
Route::get('analytics/onlineCourse', [CoordinatorController::class, 'analyticsOnlineCourse'])->name('analytics.onlineCourse');
|
||||||
|
Route::get('analytics/patent', [CoordinatorController::class, 'analyticsPatent'])->name('analytics.patent');
|
||||||
|
|
||||||
|
Route::get('analytics/comparison', [CoordinatorController::class, 'analyticsComparison'])->name('analytics.comparison');
|
||||||
|
Route::get('analytics/contribution', [CoordinatorController::class, 'analyticsContribution'])->name('analytics.contribution');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
Route::post('/admin/download-proofs', [AdminController::class, 'downloadProofs'])->name('admin.downloadProofs');
|
Route::post('/admin/download-proofs', [AdminController::class, 'downloadProofs'])->name('admin.downloadProofs');
|
||||||
|
Route::post('/admin/download-proofs', [CoordinatorController::class, 'downloadProofs'])->name('admin.downloadProofs');
|
||||||
|
|
||||||
// Google Login Route
|
// Google Login Route
|
||||||
Route::get('/auth/google', [App\Http\Controllers\Auth\GoogleController::class, 'redirectToGoogle'])->name('google.login');
|
Route::get('/auth/google', [App\Http\Controllers\Auth\GoogleController::class, 'redirectToGoogle'])->name('google.login');
|
||||||
|
|||||||
Reference in New Issue
Block a user