From ec5bb4cd8b13b1c1ed974412abb4f04f53ac3df4 Mon Sep 17 00:00:00 2001 From: AgustinUno Date: Sat, 4 Apr 2026 09:26:09 +0800 Subject: [PATCH 1/9] refactor: enable default CORS configuration in vite.config.js --- vite.config.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/vite.config.js b/vite.config.js index 558d1681..a101e846 100644 --- a/vite.config.js +++ b/vite.config.js @@ -52,14 +52,7 @@ export default defineConfig(({ mode }) => ({ host: '0.0.0.0', port: 5173, strictPort: true, - cors: { - origin: [ - 'http://localhost:8000', - 'http://127.0.0.1:8000', - `http://${LAN_IP}:8000`, - ], - credentials: true, - }, + cors: true, hmr: { host: LAN_IP, }, From f9836b8d883eb8fdafa24812d510f8c7572669e2 Mon Sep 17 00:00:00 2001 From: AgustinUno Date: Sat, 4 Apr 2026 18:14:39 +0800 Subject: [PATCH 2/9] feat: add CORS configuration and extend Auth type with index signature --- config/cors.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 config/cors.php diff --git a/config/cors.php b/config/cors.php new file mode 100644 index 00000000..b6698525 --- /dev/null +++ b/config/cors.php @@ -0,0 +1,33 @@ + ['*'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 0, + + 'supports_credentials' => true, +]; From 6a6f6b9603fbeecb4c42a856fb49e69e006aef5c Mon Sep 17 00:00:00 2001 From: AgustinUno Date: Sat, 4 Apr 2026 18:14:45 +0800 Subject: [PATCH 3/9] d --- resources/js/types/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/js/types/index.ts b/resources/js/types/index.ts index 86f1e6a3..c1118358 100644 --- a/resources/js/types/index.ts +++ b/resources/js/types/index.ts @@ -4,6 +4,7 @@ import { FacultyStaff } from './content'; export interface Auth { user: User; programs: ProgramPrivilege; + [key: string]: unknown; } export interface BreadcrumbItem { From 1994e09482277d6462c295e03b1506f27fd83a94 Mon Sep 17 00:00:00 2001 From: AgustinUno Date: Sat, 4 Apr 2026 18:29:16 +0800 Subject: [PATCH 4/9] chore: liberalize Content Security Policy to allow all origins for development convenience --- app/Http/Middleware/SecurityHeaders.php | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/app/Http/Middleware/SecurityHeaders.php b/app/Http/Middleware/SecurityHeaders.php index 9dd14048..85febab4 100644 --- a/app/Http/Middleware/SecurityHeaders.php +++ b/app/Http/Middleware/SecurityHeaders.php @@ -29,22 +29,11 @@ public function handle(Request $request, Closure $next): Response // Referrer Policy $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin'); - // Content Security Policy (Basic starting point) - // We allow self, and some common CDNs/Vite dev server for convenience - $csp = "default-src 'self'; "; - - $viteDevServer = "http://192.168.1.23:5173 http://localhost:5173 ws://192.168.1.23:5173 ws://localhost:5173"; - $scriptSources = "'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net " . $viteDevServer; - $styleSources = "'self' 'unsafe-inline' https://fonts.googleapis.com " . $viteDevServer; - $connectSources = "'self' ws: wss: " . $viteDevServer; - - $csp .= "script-src " . $scriptSources . "; "; - $csp .= "style-src " . $styleSources . "; "; - $csp .= "font-src 'self' https://fonts.gstatic.com " . $viteDevServer . "; "; - $csp .= "img-src 'self' data: blob: " . $viteDevServer . "; "; - $csp .= "connect-src " . $connectSources . "; "; - $csp .= "frame-src 'self' https://www.google.com https://*.google.com; "; - $csp .= "frame-ancestors 'self'; "; + // Content Security Policy (Liberalized for Development) + // We allow all origins (*) to ensure external resources like Facebook, YouTube, and Google Maps are not blocked. + $csp = "default-src 'self' * 'unsafe-inline' 'unsafe-eval' data: blob:; "; + $csp .= "frame-src 'self' *; "; + $csp .= "frame-ancestors 'self' *; "; $csp .= "object-src 'none';"; $response->headers->set('Content-Security-Policy', $csp); From d5e37bca16b0c06059c74c79657f6b80a6e4eed3 Mon Sep 17 00:00:00 2001 From: AgustinUno Date: Sat, 4 Apr 2026 18:47:39 +0800 Subject: [PATCH 5/9] Reapply "Merge branch 'master' of https://github.com/TombstonePUP/PUPCON" This reverts commit 27a0aa2b6f50cfa2f8dc46845519a435e4637006. --- .../Controllers/Files/AreaFilesController.php | 65 +-- .../Controllers/Files/AreaFormsController.php | 5 +- app/Http/Controllers/TestingShits.php | 64 --- app/Http/Middleware/SecurityHeaders.php | 54 ++ bootstrap/app.php | 3 + composer.json | 1 - composer.lock | 73 +-- config/pdf-optimizer.php | 16 - knip.json | 20 + package.json | 10 +- resources/js/app.tsx | 8 +- resources/js/components/app-header.tsx | 182 ------- resources/js/components/app-logo-icon.tsx | 13 - resources/js/components/app-logo.tsx | 14 - .../js/components/appearance-dropdown.tsx | 53 -- resources/js/components/column-header.tsx | 66 --- resources/js/components/column-toggle.tsx | 59 -- .../components/content/facilities-content.tsx | 1 - .../components/content/history/directors.tsx | 1 - .../content/local-task-force-content.tsx | 1 - .../content/program/program-section.tsx | 1 - .../js/components/content/vmgo-content.tsx | 1 - .../dashboard/areas/parameter-accordion.tsx | 107 ++-- .../data-table-header.tsx | 67 --- .../dialogs/area-forms/upload-area-form.tsx | 231 +++++--- .../dialogs/content/faculty-dialog.tsx | 4 - .../content/programs/end-survey-dialog.tsx | 1 - .../gallery/delete-gallery-dialog.tsx | 17 - .../objectives/delete-objective-dialog.tsx | 17 - .../content/programs/program-dialog.tsx | 1 - .../content/programs/program-level-dialog.tsx | 1 - .../task-force-area-official-dialog.tsx | 4 - .../dialogs/documents/upload-document.tsx | 287 ++++++---- .../delete-document-exhibit-dialog.tsx | 4 - .../document-exhibit-dialog-renderer.tsx | 0 .../document/document-exhibit-dialog.tsx | 305 ++++++----- .../outline/exhibit-outline-dialog.tsx | 3 - .../dialogs/requests/reject-document.tsx | 1 - .../requests/request-dialog-renderer.tsx | 1 - .../js/components/dialogs/users/add-user.tsx | 512 ++++++++++-------- resources/js/components/error-boundary.tsx | 67 +++ resources/js/components/icon.tsx | 10 - resources/js/components/image-uploader.tsx | 90 +-- resources/js/components/imageuploader.tsx | 173 ------ resources/js/components/nav-footer.tsx | 33 -- resources/js/components/pagination.tsx | 101 ---- resources/js/components/recursive-outline.tsx | 127 +++-- resources/js/components/tour/guide-tour.tsx | 63 +++ resources/js/components/tour/tour-config.ts | 139 +++++ resources/js/components/tour/tour-context.tsx | 44 ++ resources/js/components/tour/tour-mask.tsx | 77 +++ resources/js/components/tour/tour-tooltip.tsx | 116 ++++ resources/js/hooks/use-image-compressor.ts | 115 ++++ resources/js/hooks/use-pdf-compressor.ts | 178 ++++++ resources/js/layouts/accreditor-layout.tsx | 29 +- resources/js/layouts/app-layout.tsx | 30 +- .../js/layouts/app/app-header-layout.tsx | 18 - resources/js/layouts/auth-layout.tsx | 29 +- .../js/layouts/auth/auth-card-layout.tsx | 36 -- .../js/layouts/auth/auth-simple-layout.tsx | 87 +-- .../js/layouts/auth/auth-split-layout.tsx | 45 -- .../js/layouts/auth/auth-splitCard-layout.tsx | 60 -- resources/js/layouts/landing-layout.tsx | 13 +- resources/js/pages/about/faculty.tsx | 1 - .../js/pages/accreditor/accreditor-view.tsx | 2 - resources/js/pages/area.tsx | 213 ++++---- resources/js/pages/auth/login.tsx | 24 +- resources/js/pages/dashboard.tsx | 2 - resources/js/pages/document/ratings.tsx | 2 - resources/js/pages/settings/archive.tsx | 2 - resources/js/pages/test/GuideTour.tsx | 157 ------ resources/js/pages/test/shepherd-custom.scss | 160 ------ resources/js/pages/user-management.tsx | 4 +- resources/js/pages/welcome.tsx | 3 - resources/js/types/files.ts | 0 resources/js/types/guest.ts | 0 resources/js/types/index.ts | 108 +--- resources/js/types/toast.ts | 9 - resources/js/workers/pdf-compressor.worker.ts | 177 ++++++ .../views/vendor/mail/html/header.blade.php | 6 +- .../views/vendor/mail/html/themes/default.css | 11 +- vite.config.js | 6 +- 82 files changed, 2368 insertions(+), 2473 deletions(-) delete mode 100644 app/Http/Controllers/TestingShits.php create mode 100644 app/Http/Middleware/SecurityHeaders.php delete mode 100644 config/pdf-optimizer.php create mode 100644 knip.json delete mode 100644 resources/js/components/app-header.tsx delete mode 100644 resources/js/components/app-logo-icon.tsx delete mode 100644 resources/js/components/app-logo.tsx delete mode 100644 resources/js/components/appearance-dropdown.tsx delete mode 100644 resources/js/components/column-header.tsx delete mode 100644 resources/js/components/column-toggle.tsx delete mode 100644 resources/js/components/data-table-components/data-table-header.tsx delete mode 100644 resources/js/components/dialogs/content/programs/gallery/delete-gallery-dialog.tsx delete mode 100644 resources/js/components/dialogs/content/programs/objectives/delete-objective-dialog.tsx delete mode 100644 resources/js/components/dialogs/exhibits/document/delete-document-exhibit-dialog.tsx delete mode 100644 resources/js/components/dialogs/exhibits/document/document-exhibit-dialog-renderer.tsx create mode 100644 resources/js/components/error-boundary.tsx delete mode 100644 resources/js/components/icon.tsx delete mode 100644 resources/js/components/imageuploader.tsx delete mode 100644 resources/js/components/nav-footer.tsx delete mode 100644 resources/js/components/pagination.tsx create mode 100644 resources/js/components/tour/guide-tour.tsx create mode 100644 resources/js/components/tour/tour-config.ts create mode 100644 resources/js/components/tour/tour-context.tsx create mode 100644 resources/js/components/tour/tour-mask.tsx create mode 100644 resources/js/components/tour/tour-tooltip.tsx create mode 100644 resources/js/hooks/use-image-compressor.ts create mode 100644 resources/js/hooks/use-pdf-compressor.ts delete mode 100644 resources/js/layouts/app/app-header-layout.tsx delete mode 100644 resources/js/layouts/auth/auth-card-layout.tsx delete mode 100644 resources/js/layouts/auth/auth-split-layout.tsx delete mode 100644 resources/js/layouts/auth/auth-splitCard-layout.tsx delete mode 100644 resources/js/pages/test/GuideTour.tsx delete mode 100644 resources/js/pages/test/shepherd-custom.scss delete mode 100644 resources/js/types/files.ts delete mode 100644 resources/js/types/guest.ts delete mode 100644 resources/js/types/toast.ts create mode 100644 resources/js/workers/pdf-compressor.worker.ts diff --git a/app/Http/Controllers/Files/AreaFilesController.php b/app/Http/Controllers/Files/AreaFilesController.php index ec398a15..ce3c2064 100644 --- a/app/Http/Controllers/Files/AreaFilesController.php +++ b/app/Http/Controllers/Files/AreaFilesController.php @@ -16,8 +16,7 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Support\Str; use Illuminate\Support\Facades\Storage; -use Illuminate\Support\Facades\Log; -use Mostafaznv\PdfOptimizer\Laravel\Facade\PdfOptimizer; +// Compression is handled client-side before upload — no server-side optimizer needed. class AreaFilesController extends Controller @@ -29,8 +28,11 @@ public function store(Request $request) { $validated = $request->validate( [ - 'outline_id' => 'nullable|exists:parameter_outlines,parameter_outline_id', - 'document' => 'required|file|mimes:pdf' + 'outline_id' => 'required|integer|exists:parameter_outlines,parameter_outline_id', + 'document' => 'required|file|mimes:pdf|max:10240', + 'program_id' => 'required|integer|exists:programs,program_id', + 'level_id' => 'required|integer|exists:accreditation_levels,accreditation_level_id', + 'area_id' => 'required|integer|exists:areas,area_id', ], [ 'outline_id.exists' => 'The selected outline does not exist.', @@ -88,57 +90,8 @@ public function store(Request $request) $category_name = Str::slug($categoryName, '_'); $filePath = "documents/{$degree_type}_{$program_name}/{$level}/{$area_name}/{$parameter_name}/{$category_name}"; - // Ensure temp directory exists - if (!Storage::disk('public')->exists('temp')) { - Storage::disk('public')->makeDirectory('temp'); - } - - // Store original file temporarily - $tempFileName = 'temp_' . Str::uuid() . '.pdf'; - $tempFilePath = "temp/{$tempFileName}"; - Storage::disk('public')->putFileAs('temp', $file, $tempFileName); - - Log::info('Temp file created: ' . $tempFilePath); - Log::info('Temp file size: ' . Storage::disk('public')->size($tempFilePath) . ' bytes'); - - // Optimize PDF - try { - Log::info('Starting PDF optimization...'); - $customTempPath = str_replace('/', '\\', storage_path('app/public/temp')); - putenv('TMP=' . $customTempPath); - putenv('TEMP=' . $customTempPath); - - Log::info('Using custom TEMP/TMP path: ' . $customTempPath); - - $result = PdfOptimizer::fromDisk('public') - ->open($tempFilePath) - ->toDisk('public') - ->optimize("{$filePath}/{$fileName}"); - - Log::info('Optimization status: ' . ($result->status ? 'SUCCESS' : 'FAILED')); - Log::info('Optimization message: ' . $result->message); - - if (Storage::disk('public')->exists("{$filePath}/{$fileName}")) { - Log::info('Optimized file created successfully'); - Log::info('Optimized file size: ' . Storage::disk('public')->size("{$filePath}/{$fileName}") . ' bytes'); - } else { - Log::error('Optimized file NOT found!'); - } - - // Delete temporary file - Storage::disk('public')->delete($tempFilePath); - Log::info('Temp file deleted'); - - if (!$result->status) { - Storage::disk('public')->putFileAs($filePath, $file, $fileName); - Log::warning('PDF optimization failed, using original file: ' . $result->message); - } - } catch (\Exception $e) { - Storage::disk('public')->delete($tempFilePath); - Storage::disk('public')->putFileAs($filePath, $file, $fileName); - Log::error('PDF optimization error: ' . $e->getMessage()); - Log::error('Stack trace: ' . $e->getTraceAsString()); - } + // PDF is already compressed by the client before upload — store directly. + Storage::disk('public')->putFileAs($filePath, $file, $fileName); $areaFile = $parameterOutlines->AreaFiles()->create([ 'file_name' => $fileName, @@ -169,7 +122,7 @@ public function download(Request $request) $areaFile = $parameterOutlines->AreaFiles; if ($areaFile && Storage::disk('public')->exists($areaFile->file_path)) { - return Storage::disk('public')->download($areaFile->file_path, $areaFile->file_name); + return response()->download(storage_path("app/public/" . $areaFile->file_path), $areaFile->file_name); } else { return redirect()->back() ->with('type', 'error') diff --git a/app/Http/Controllers/Files/AreaFormsController.php b/app/Http/Controllers/Files/AreaFormsController.php index 9feee10d..2990e223 100644 --- a/app/Http/Controllers/Files/AreaFormsController.php +++ b/app/Http/Controllers/Files/AreaFormsController.php @@ -27,7 +27,10 @@ public function store(Request $request): RedirectResponse $validated = $request->validate( [ 'area_form_category_id' => 'required|integer|exists:area_form_categories,area_form_category_id', - 'document' => 'nullable|file|mimes:pdf', + 'document' => 'nullable|file|mimes:pdf|max:10240', + 'program_id' => 'required|integer|exists:programs,program_id', + 'level_id' => 'required|integer|exists:accreditation_levels,accreditation_level_id', + 'area_id' => 'required|integer|exists:areas,area_id', ], [ 'area_form_category_id.required' => 'Form category ID is required.', diff --git a/app/Http/Controllers/TestingShits.php b/app/Http/Controllers/TestingShits.php deleted file mode 100644 index 949c6378..00000000 --- a/app/Http/Controllers/TestingShits.php +++ /dev/null @@ -1,64 +0,0 @@ -headers->set('X-Frame-Options', 'SAMEORIGIN'); + + // Prevent MIME-type sniffing + $response->headers->set('X-Content-Type-Options', 'nosniff'); + + // Basic XSS protection (though modern browsers handle this better) + $response->headers->set('X-XSS-Protection', '1; mode=block'); + + // Referrer Policy + $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin'); + + // Content Security Policy (Basic starting point) + // We allow self, and some common CDNs/Vite dev server for convenience + $csp = "default-src 'self'; "; + + $viteDevServer = "http://192.168.1.23:5173 http://localhost:5173 ws://192.168.1.23:5173 ws://localhost:5173"; + $scriptSources = "'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net " . $viteDevServer; + $styleSources = "'self' 'unsafe-inline' https://fonts.googleapis.com " . $viteDevServer; + $connectSources = "'self' ws: wss: " . $viteDevServer; + + $csp .= "script-src " . $scriptSources . "; "; + $csp .= "style-src " . $styleSources . "; "; + $csp .= "font-src 'self' https://fonts.gstatic.com " . $viteDevServer . "; "; + $csp .= "img-src 'self' data: blob: " . $viteDevServer . "; "; + $csp .= "connect-src " . $connectSources . "; "; + $csp .= "frame-src 'self' https://www.google.com https://*.google.com; "; + $csp .= "frame-ancestors 'self'; "; + $csp .= "object-src 'none';"; + + $response->headers->set('Content-Security-Policy', $csp); + + return $response; + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 00140136..6f5e0c08 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -14,6 +14,8 @@ use App\Http\Middleware\UserProgramPrivileges; use App\Http\Middleware\UserAreaPrivileges; +use App\Http\Middleware\SecurityHeaders; + return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', @@ -25,6 +27,7 @@ $middleware->web(append: [ HandleInertiaRequests::class, AddLinkHeadersForPreloadedAssets::class, + SecurityHeaders::class, ]); /* $middleware->api(prepend: [ \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, diff --git a/composer.json b/composer.json index e24af291..c948bb61 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,6 @@ "laravel/reverb": "^1.0", "laravel/tinker": "^2.10.1", "league/csv": "^9.27", - "mostafaznv/pdf-optimizer": "^1.2", "tightenco/ziggy": "^2.4" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 6c3921a9..24391352 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "512f2965bfdce6e47d5f06d5776ab40f", + "content-hash": "2c2d5b9ea275513741f7f23fe3625a48", "packages": [ { "name": "brick/math", @@ -2539,77 +2539,6 @@ ], "time": "2025-03-24T10:02:05+00:00" }, - { - "name": "mostafaznv/pdf-optimizer", - "version": "1.2.4", - "source": { - "type": "git", - "url": "https://github.com/mostafaznv/pdf-optimizer.git", - "reference": "2fb124ca06f57b762ed3b03b4cad9c850cba5a06" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mostafaznv/pdf-optimizer/zipball/2fb124ca06f57b762ed3b03b4cad9c850cba5a06", - "reference": "2fb124ca06f57b762ed3b03b4cad9c850cba5a06", - "shasum": "" - }, - "require": { - "ext-fileinfo": "*", - "php": "^8.2", - "psr/log": "^3.0", - "symfony/process": "^6.2|^7.0" - }, - "require-dev": { - "league/flysystem-aws-s3-v3": "^3.0", - "orchestra/testbench": "^v8.19.0|^9.0|^10.0", - "pestphp/pest": "^2.0|^3.7", - "phpunit/phpunit": "^10.0|^11.5.3" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Mostafaznv\\PdfOptimizer\\PdfOptimizerServiceProvider" - ] - } - }, - "autoload": { - "files": [ - "src/Helpers/Utils.php" - ], - "psr-4": { - "Mostafaznv\\PdfOptimizer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "mostafaznv", - "email": "mostafa.zeinivand@gmail.com" - } - ], - "description": "PDF optimization tool for PHP and Laravel applications", - "keywords": [ - "compress-pdf", - "ghostscript", - "laravel", - "mostafaznv", - "pdf", - "pdf-compressor", - "pdf-optimizer", - "php", - "tiny-pdf" - ], - "support": { - "docs": "https://github.com/mostafaznv/pdf-optimizer/blob/master/README.md", - "issues": "https://github.com/mostafaznv/pdf-optimizer/issues", - "source": "https://github.com/mostafaznv/pdf-optimizer" - }, - "time": "2025-09-24T21:51:40+00:00" - }, { "name": "nesbot/carbon", "version": "3.11.0", diff --git a/config/pdf-optimizer.php b/config/pdf-optimizer.php deleted file mode 100644 index 6de8e549..00000000 --- a/config/pdf-optimizer.php +++ /dev/null @@ -1,16 +0,0 @@ - [ - 'binary' => env('PDF_OPTIMIZER_BIN_PATH', 'C:\\Program Files\\gs\\gs10.06.0\\bin\\gswin64c.exe'), - ], - 'options' => [ - '-dBATCH', - '-dNOPAUSE', - '-q', - '-sDEVICE=pdfwrite', - '-dPDFSETTINGS=/screen', - '-sTEMP=C:\\Users\\LENOVO\\Desktop\\Capstone\\PUPCON\\storage\\app\\public\\temp', - //'-sTEMP=C:\\Users\\LENOVO\\Desktop\\Capstone\\PUPCON\\storage\\app\\public\\temp', - ], -]; diff --git a/knip.json b/knip.json new file mode 100644 index 00000000..e47f974c --- /dev/null +++ b/knip.json @@ -0,0 +1,20 @@ +{ + "entry": [ + "resources/js/app.tsx", + "resources/js/ssr.jsx", + "resources/scss/app.scss", + "resources/js/components/ui/**/*.{ts,tsx}", + "resources/js/echo.js" + ], + "project": ["resources/js/**/*.{js,jsx,ts,tsx}", "resources/scss/**/*.scss", "resources/css/**/*.css"], + "ignoreDependencies": [ + "concurrently", + "@inertiajs/core", + "zod", + "date-fns" + ], + "rules": { + "exports": "off", + "types": "off" + } +} diff --git a/package.json b/package.json index 6b17b606..e47908d0 100644 --- a/package.json +++ b/package.json @@ -25,12 +25,7 @@ "typescript-eslint": "^8.23.0" }, "dependencies": { - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/modifiers": "^9.0.0", - "@dnd-kit/sortable": "^10.0.0", - "@dnd-kit/utilities": "^3.2.2", "@headlessui/react": "^2.2.0", - "@hookform/resolvers": "^5.2.2", "@inertiajs/core": "^2.2.19", "@inertiajs/react": "^2.2.19", "@radix-ui/react-accordion": "^1.2.3", @@ -53,7 +48,6 @@ "@radix-ui/react-toggle": "^1.1.2", "@radix-ui/react-toggle-group": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.8", - "@tabler/icons-react": "^3.34.0", "@tailwindcss/vite": "^4.0.6", "@tanstack/react-table": "^8.21.3", "@types/react-dom": "^19.0.2", @@ -70,6 +64,8 @@ "lucide-react": "^0.475.0", "next-themes": "^0.4.6", "nprogress": "^0.2.0", + "pdf-lib": "^1.17.1", + "pdfjs-dist": "^5.6.205", "react": "^19.1.0", "react-day-picker": "^9.11.2", "react-dom": "^19.1.0", @@ -77,7 +73,6 @@ "react-loading-skeleton": "^3.5.0", "react-pdf": "^10.2.0", "recharts": "^2.15.2", - "shepherd.js": "^14.5.1", "sonner": "^2.0.7", "tailwind-merge": "^3.0.1", "tailwindcss": "^4.0.0", @@ -85,6 +80,7 @@ "typescript": "^5.7.2", "vaul": "^1.1.2", "vite": "^6.0", + "ziggy-js": "^2.6.2", "zod": "^3.25.76" }, "optionalDependencies": { diff --git a/resources/js/app.tsx b/resources/js/app.tsx index 12fefc32..48cfb569 100644 --- a/resources/js/app.tsx +++ b/resources/js/app.tsx @@ -11,13 +11,19 @@ declare global { const route: typeof routeFn; } +import ErrorBoundary from './components/error-boundary'; + createInertiaApp({ title: (title) => `${title}`, resolve: (name) => resolvePageComponent(`./pages/${name}.tsx`, import.meta.glob('./pages/**/*.tsx')), setup({ el, App, props }) { const root = createRoot(el); - root.render(); + root.render( + + + + ); }, progress: { color: '#daa520', // Your brand color diff --git a/resources/js/components/app-header.tsx b/resources/js/components/app-header.tsx deleted file mode 100644 index 4c3510dd..00000000 --- a/resources/js/components/app-header.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import { Breadcrumbs } from '@/components/breadcrumbs'; -import { Icon } from '@/components/icon'; -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { Button } from '@/components/ui/button'; -import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; -import { NavigationMenu, NavigationMenuItem, NavigationMenuList, navigationMenuTriggerStyle } from '@/components/ui/navigation-menu'; -import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; -import { UserMenuContent } from '@/components/user-menu-content'; -import { useInitials } from '@/hooks/use-initials'; -import { cn } from '@/lib/utils'; -import { type BreadcrumbItem, type NavItem, type SharedData } from '@/types'; -import { Link, usePage } from '@inertiajs/react'; -import { BookOpen, Folder, LayoutGrid, Menu, Search } from 'lucide-react'; -import AppLogo from './app-logo'; -import AppLogoIcon from './app-logo-icon'; - -const mainNavItems: NavItem[] = [ - { - title: 'Analytics', - url: '/dashboard', - icon: LayoutGrid, - }, -]; - -const rightNavItems: NavItem[] = [ - { - title: 'Repository', - url: 'https://github.com/laravel/react-starter-kit', - icon: Folder, - }, - { - title: 'Documentation', - url: 'https://laravel.com/docs/starter-kits', - icon: BookOpen, - }, -]; - -const activeItemStyles = 'text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100'; - -interface AppHeaderProps { - breadcrumbs?: BreadcrumbItem[]; -} - -export function AppHeader({ breadcrumbs = [] }: AppHeaderProps) { - const page = usePage(); - const { auth } = page.props; - const getInitials = useInitials(); - return ( - <> -
-
- {/* Mobile Menu */} -
- - - - - - Navigation Menu - - - -
-
-
- {mainNavItems.map((item) => ( - - {item.icon && } - {item.title} - - ))} -
- -
- {rightNavItems.map((item) => ( - - {item.icon && } - {item.title} - - ))} -
-
-
-
-
-
- - - - - - {/* Desktop Navigation */} -
- - - {mainNavItems.map((item, index) => ( - - - {item.icon && } - {item.title} - - {page.url === item.url && ( -
- )} -
- ))} -
-
-
- -
-
- -
- {rightNavItems.map((item) => ( - - - - - {item.title} - {item.icon && } - - - -

{item.title}

-
-
-
- ))} -
-
- - - - - - - - -
-
-
- {breadcrumbs.length > 1 && ( -
-
- -
-
- )} - - ); -} diff --git a/resources/js/components/app-logo-icon.tsx b/resources/js/components/app-logo-icon.tsx deleted file mode 100644 index 9bd62ad8..00000000 --- a/resources/js/components/app-logo-icon.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { SVGAttributes } from 'react'; - -export default function AppLogoIcon(props: SVGAttributes) { - return ( - - - - ); -} diff --git a/resources/js/components/app-logo.tsx b/resources/js/components/app-logo.tsx deleted file mode 100644 index a03784cc..00000000 --- a/resources/js/components/app-logo.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import AppLogoIcon from './app-logo-icon'; - -export default function AppLogo() { - return ( - <> -
- -
-
- Laravel Starter Kit -
- - ); -} diff --git a/resources/js/components/appearance-dropdown.tsx b/resources/js/components/appearance-dropdown.tsx deleted file mode 100644 index 89a45862..00000000 --- a/resources/js/components/appearance-dropdown.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Button } from '@/components/ui/button'; -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; -import { useAppearance } from '@/hooks/use-appearance'; -import { Monitor, Moon, Sun } from 'lucide-react'; -import { HTMLAttributes } from 'react'; - -export default function AppearanceToggleDropdown({ className = '', ...props }: HTMLAttributes) { - const { appearance, updateAppearance } = useAppearance(); - - const getCurrentIcon = () => { - switch (appearance) { - case 'dark': - return ; - case 'light': - return ; - default: - return ; - } - }; - - return ( -
- - - - - - updateAppearance('light')}> - - - Light - - - updateAppearance('dark')}> - - - Dark - - - updateAppearance('system')}> - - - System - - - - -
- ); -} diff --git a/resources/js/components/column-header.tsx b/resources/js/components/column-header.tsx deleted file mode 100644 index b70d3294..00000000 --- a/resources/js/components/column-header.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Column } from "@tanstack/react-table" -import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from "lucide-react" - -import { cn } from "@/lib/utils" -import { Button } from "@/components/ui/button" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" - -interface DataTableColumnHeaderProps - extends React.HTMLAttributes { - column: Column - title: string -} - -export function DataTableColumnHeader({ - column, - title, - className, -}: DataTableColumnHeaderProps) { - if (!column.getCanSort()) { - return
{title}
- } - - return ( -
- - - - - - column.toggleSorting(false)}> - - Asc - - column.toggleSorting(true)}> - - Desc - - - column.toggleVisibility(false)}> - - Hide - - - -
- ) -} diff --git a/resources/js/components/column-toggle.tsx b/resources/js/components/column-toggle.tsx deleted file mode 100644 index b6b16847..00000000 --- a/resources/js/components/column-toggle.tsx +++ /dev/null @@ -1,59 +0,0 @@ -"use client" - -import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu" -import { Table } from "@tanstack/react-table" -import { ColumnsIcon } from "lucide-react" - -import { Button } from "@/components/ui/button" -import { - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuLabel, - DropdownMenuSeparator, -} from "@/components/ui/dropdown-menu" - -export function DataTableViewOptions({ - table, -}: { - table: Table -}) { - return ( - - - - - - Toggle columns - - {table - .getAllColumns() - .filter( - (column) => - typeof column.accessorFn !== "undefined" && column.getCanHide() - ) - .map((column) => { - return ( - column.toggleVisibility(!!value)} - > - {typeof column.columnDef.header === "string" - ? column.columnDef.header - : column.id.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase())} - - ) - })} - - - ) -} diff --git a/resources/js/components/content/facilities-content.tsx b/resources/js/components/content/facilities-content.tsx index a5d4d115..6c364cce 100644 --- a/resources/js/components/content/facilities-content.tsx +++ b/resources/js/components/content/facilities-content.tsx @@ -263,7 +263,6 @@ const FacilitiesSection: React.FC = ({ ...props }: FacilitiesProps) => {

Select a Facility

{facilityList?.map((facility, index) => ( - // console.log('Rendering facility:', facility),
{ diff --git a/resources/js/components/content/history/directors.tsx b/resources/js/components/content/history/directors.tsx index c67f7722..ac0a0593 100644 --- a/resources/js/components/content/history/directors.tsx +++ b/resources/js/components/content/history/directors.tsx @@ -106,7 +106,6 @@ export function DirectorsSection({ ...props }: DirectorsProps) { updatedList = [...current, directorForLocalState]; } - console.log(updatedList); onUpdateDirectors(directorForLocalState, director); return updatedList; }); diff --git a/resources/js/components/content/local-task-force-content.tsx b/resources/js/components/content/local-task-force-content.tsx index d7aba491..61963e71 100644 --- a/resources/js/components/content/local-task-force-content.tsx +++ b/resources/js/components/content/local-task-force-content.tsx @@ -111,7 +111,6 @@ const LocalTaskForceSection: React.FC = ({ ...props }: LocalTaskForceSectionProp }; const handleSubmit = () => { - console.log('Submitting Local Task Force Data:', data); post(route('content.local_task_force.update'), { preserveScroll: true, preserveState: true, diff --git a/resources/js/components/content/program/program-section.tsx b/resources/js/components/content/program/program-section.tsx index b93c49f3..96dfd59e 100644 --- a/resources/js/components/content/program/program-section.tsx +++ b/resources/js/components/content/program/program-section.tsx @@ -343,7 +343,6 @@ export default function ProgramSection({ program, overviewRef, objectivesRef, ga }; const onSave = () => { - console.log('Saving program content...', data); post( route('manage.program.update.content', { program_id: program.program_id, diff --git a/resources/js/components/content/vmgo-content.tsx b/resources/js/components/content/vmgo-content.tsx index 045dec28..f65cce5c 100644 --- a/resources/js/components/content/vmgo-content.tsx +++ b/resources/js/components/content/vmgo-content.tsx @@ -85,7 +85,6 @@ const VmgoContentSection: React.FC = ({ ...props }: VmgoContentSectionProps) => }; const handleSave = () => { - console.log('Submitting VMGO Data:', data.pillars); post(route('content.vmgo.update'), { preserveScroll: true, preserveState: true, diff --git a/resources/js/components/dashboard/areas/parameter-accordion.tsx b/resources/js/components/dashboard/areas/parameter-accordion.tsx index 764dd43f..03260907 100644 --- a/resources/js/components/dashboard/areas/parameter-accordion.tsx +++ b/resources/js/components/dashboard/areas/parameter-accordion.tsx @@ -3,7 +3,7 @@ import { buildOutlineTree, RecursiveOutlineForm } from '@/components/recursive-o import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion'; import { Button } from '@/components/ui/button'; import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'; -import { Area, AreaFormCategory, type AreaParameters, type ParameterOutlineCategory, ParameterOutlines, Program } from '@/types'; +import { Area, type AreaParameters, type ParameterOutlineCategory, ParameterOutlines, Program } from '@/types'; import { usePage } from '@inertiajs/react'; import { FolderPlus, Pencil, PlusCircle, Trash2 } from 'lucide-react'; @@ -22,7 +22,7 @@ interface BenchDialogParams { interface ParamDialogParams { type: 'add' | 'import' | 'edit' | 'delete'; - parameter: AreaParameters; + parameter?: AreaParameters; } interface ParameterAccordionProps { @@ -44,12 +44,14 @@ export default function ParameterAccordion({ resolveBenchDialog, resolveParamDialog, }: ParameterAccordionProps) { - const { auth } = usePage().props; - const role = auth.user.roles.role_name; + const { auth } = usePage().props; + const role = auth?.user?.roles?.role_name; + + const activeLevel = Array.isArray(program.levels) ? program.levels[0] : program.levels; const canShowActions = (role === 'Admin' || role === 'Coordinator') && - program.levels[0]?.is_active && - program.levels[0]?.remarks === 'Ongoing Survey' && + activeLevel?.is_active && + activeLevel?.remarks === 'Ongoing Survey' && !area.archive === true; return ( @@ -59,33 +61,36 @@ export default function ParameterAccordion({ areaParameters?.map((parameter) => ( - +
-

+

{!parameter.parameter_name?.trim() ? `${parameter.parameter_description}` : `Parameter ${parameter.parameter_name.toUpperCase()[0]}` }

-

+

{parameter.parameter_name?.trim() ? parameter.parameter_description : ''}

{canShowActions && ( -
+
e.stopPropagation()}>
)} - + {parameter.parameter_outlines?.length > 0 ? ( - parameterOutlineCategories.map((category) => { - const outlines = - parameter.parameter_outlines?.filter( - (outline) => - outline.parameter_outline_category_id === - category.parameter_outline_category_id, - ) || []; - if (outlines.length === 0) return null; + parameterOutlineCategories?.map((category) => { + // Optimized filtering without in-place mutation + const filteredOutlines = (parameter.parameter_outlines || []) + .filter(o => o.parameter_outline_category_id === category.parameter_outline_category_id) + .map(o => ({ + ...o, + initial: category.category_name === 'No Category' + ? (parameter.parameter_name?.trim() ? parameter.parameter_name.toUpperCase().match(alphaRegex)?.[0] || '' : '') + : category.category_name.match(alphaRegex)?.[0] || '' + })); - outlines.map((outline) => { - outline.initial = - category.category_name === 'No Category' - ? parameter.parameter_name === '' - ? '' - : parameter.parameter_name.toUpperCase().match(alphaRegex) - : category.category_name.match(alphaRegex); - }); + if (filteredOutlines.length === 0) return null; - const sortedOutlines = buildOutlineTree({ outlines }); + const sortedOutlines = buildOutlineTree({ outlines: filteredOutlines }); return (
-

- {category.category_name === 'No Category' ? '' : category.category_name} +

+ {category.category_name === 'No Category' ? 'General Benchmarks' : category.category_name}

@@ -136,31 +135,33 @@ export default function ParameterAccordion({ ); }) ) : ( -

- No outlines available for this parameter. -

+
+ No benchmarks available for this parameter. +
)} {canShowActions && ( - +
+ +
)} )) ) : ( - + - + Content Not Available No available parameters in this area. diff --git a/resources/js/components/data-table-components/data-table-header.tsx b/resources/js/components/data-table-components/data-table-header.tsx deleted file mode 100644 index 13bf2be2..00000000 --- a/resources/js/components/data-table-components/data-table-header.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { type Column } from "@tanstack/react-table" -import { ArrowDown, ArrowUp, ChevronsUpDown } from "lucide-react" - -import { Button } from "@/components/ui/button" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import { cn } from "@/lib/utils" - -interface DataTableColumnHeaderProps - extends React.HTMLAttributes { - column: Column - title: string -} - -export function DataTableColumnHeader({ - column, - title, - className, -}: DataTableColumnHeaderProps) { - if (!column.getCanSort()) { - return
{title}
- } - - return ( -
- - - - - - column.toggleSorting(false)}> - - Asc - - column.toggleSorting(true)}> - - Desc - - {/* - column.toggleVisibility(false)}> - - Hide - */} - - -
- ) -} diff --git a/resources/js/components/dialogs/area-forms/upload-area-form.tsx b/resources/js/components/dialogs/area-forms/upload-area-form.tsx index 8e7072fe..ad5aa27e 100644 --- a/resources/js/components/dialogs/area-forms/upload-area-form.tsx +++ b/resources/js/components/dialogs/area-forms/upload-area-form.tsx @@ -2,12 +2,13 @@ import InputError from '@/components/input-error'; import { Button } from '@/components/ui/button'; -import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; -import { Label } from '@/components/ui/label'; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogClose } from '@/components/ui/dialog'; import { Progress } from '@/components/ui/progress'; +import { formatBytes, usePdfCompressor } from '@/hooks/use-pdf-compressor'; import { AreaForms, Program } from '@/types'; import { useForm } from '@inertiajs/react'; -import React from 'react'; +import { CheckCircle2, FileText, Loader2, Upload, X } from 'lucide-react'; +import React, { useState } from 'react'; import { toast } from 'sonner'; interface UploadAreaFormProps { @@ -26,29 +27,46 @@ export function UploadAreaForm({ program, form, area_id, onClose }: UploadAreaFo document: null, }); - console.log(form); + const { compress, isCompressing, progress } = usePdfCompressor(); + const [compressionInfo, setCompressionInfo] = useState<{ savedPercent: number; compressedSize: number } | null>(null); - const [isUploading, setIsUploading] = React.useState(false); + const handleFileChange = async (file: File | null) => { + if (!file) { + setData('document', null); + setCompressionInfo(null); + return; + } + + const result = await compress(file); + setData('document', result.file); + + if (!result.skipped && result.savedPercent > 0) { + setCompressionInfo({ savedPercent: result.savedPercent, compressedSize: result.compressedSize }); + } + }; const uploadAreaForm = (e: React.FormEvent) => { - console.log(data); e.preventDefault(); - setIsUploading(true); + + const programLevelId = Array.isArray(program.levels) + ? (program.levels as any)[0]?.accreditation_level_id + : (program.levels as any)?.accreditation_level_id; + post( route('manage.area.upload.area.form.file', { program_id: program.program_id, - level_id: program.levels[0]?.accreditation_level_id, + level_id: programLevelId, area_id: area_id, form_id: form.area_form_id, }), { - onProgress: (progress) => { - if (progress?.percentage) { + onProgress: (p) => { + if (p?.percentage) { toast.info('Uploading...', { description: (
- -

{progress.percentage}%

+ +

{p.percentage}%

), id: 'uploading', @@ -58,7 +76,7 @@ export function UploadAreaForm({ program, form, area_id, onClose }: UploadAreaFo onSuccess: () => { toast.dismiss('uploading'); reset(); - setIsUploading(false); + setCompressionInfo(null); onClose(); }, onError: (errors) => { @@ -66,85 +84,150 @@ export function UploadAreaForm({ program, form, area_id, onClose }: UploadAreaFo toast.error('Failed to upload document', { description: errors.document ?? 'There was an error uploading the document.', }); - setIsUploading(false); }, }, ); }; + const isBusy = isCompressing || processing; + return ( - onClose()}> - - - {form.file_name ? 'Update' : 'Upload'} Document - Upload a Document for this card + !open && onClose()}> + + {/* ── Header ── */} + +
+
+ +
+
+ + {form.file_name ? 'Update' : 'Upload'} Document + + + Upload a document for this card + +
+ +
-
-
-
-
- {!data.document ? ( -