Updated Google Scholar Integration,Updated Admin and HOD Dashboard, Added Google Auth: Login Controller and Routes

This commit is contained in:
tanmaychinchore
2025-11-18 22:24:53 +05:30
parent 9ee3d3eda4
commit 34394e6abf
9 changed files with 1347 additions and 49 deletions

View File

@@ -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
] ]
); );

View File

@@ -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
]);
} }
} }

View File

@@ -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 (empty($publications)) { // If the service returned an error payload, forward it to UI
return response()->json([ if (is_array($result) && array_key_exists('error', $result)) {
'success' => false, // Log more details if available
'message' => 'No publications found from the provided Scholar profile.' 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)) {
return response()->json([
'error' => '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) {

View File

@@ -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 = '';

View File

@@ -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 [];

View File

@@ -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
View File

@@ -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"
} }

View File

@@ -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> </div>
<!-- Graphs Section -->
<div class="mt-6"> <div class="mt-6">
<a href="{{ route('departments.index') }}" class="text-blue-500 underline">View Departments</a><br> <h3 class="text-lg font-semibold">Analytics</h3>
<a href="{{ route('users.index') }}" class="text-blue-500 underline">View Faculty</a><br> <div id="graphs-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-6 mt-4">
<a href="{{ route('profile.edit') }}" class="text-blue-500 underline">Edit Profile</a> <!-- 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> </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

View File

@@ -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');