- {area.area_forms?.length > 0 ? (
+ {(area as any).area_forms?.length > 0 ? (
- {area.area_forms?.map((area_form) => (
+ {(area as any).area_forms?.map((area_form: any) => (
- {area.area_parameters?.length > 0 ? (
+ {(area as any).area_parameters?.length > 0 ? (
- {[...area.area_parameters]
+ {[...(area as any).area_parameters]
.sort((a, b) => {
if (a.parameter_name?.trim().toUpperCase() === 'A') return -1;
if (b.parameter_name?.trim().toUpperCase() === 'A') return 1;
return a.parameter_name?.localeCompare(b.parameter_name || '') || 0;
})
- .map((parameter) => {
+ .map((parameter: any) => {
const paramKey = parameter.parameter_id ?? parameter.area_parameter_id;
+ const hasOutlines = categories.some((category) => {
+ const outlines =
+ parameter.parameter_outlines?.filter(
+ (outline: any) =>
+ outline.parameter_outline_category_id ===
+ category.parameter_outline_category_id,
+ ) || [];
+ return outlines.length > 0;
+ });
+
return (
- (parameterRef.current[paramKey] = el)}
- >
-
{ parameterRef.current[paramKey] = el; }}
>
-
-
-
- {!parameter.parameter_name?.trim()
- ? `${parameter.parameter_description}`
- : `Parameter ${parameter.parameter_name.toUpperCase()[0]}`
- }
-
-
- {parameter.parameter_name?.trim() ? parameter.parameter_description : ''}
-
-
-
-
- {categories.some((category) => {
- const outlines =
- parameter.parameter_outlines?.filter(
- (outline) =>
- outline.parameter_outline_category_id ===
- category.parameter_outline_category_id,
- ) || [];
- return outlines.length > 0;
- }) ? (
- categories.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;
+
+
+
+
+ {!parameter.parameter_name?.trim()
+ ? `${parameter.parameter_description}`
+ : `Parameter ${parameter.parameter_name.toUpperCase()[0]}`
+ }
+
+
+ {parameter.parameter_name?.trim() ? parameter.parameter_description : ''}
+
+
+
+
+ {hasOutlines ? (
+ categories.map((category) => {
+ const filteredOutlines = (parameter.parameter_outlines || [])
+ .filter((o: any) => o.parameter_outline_category_id === category.parameter_outline_category_id)
+ .map((o: any) => ({
+ ...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}
-
-
-
- );
- })
- ) : (
-
-
-
-
No outline available for this parameter
-
- Content will be added during the accreditation process
-
-
-
- )}
-
-
-
+ return (
+
+
+ {category.category_name === 'No Category' ? '' : category.category_name}
+
+ {
+ if (params.type === 'view') {
+ openViewer(params.benchmark.area_files?.file_path || '', params.benchmark.outline_description || '');
+ }
+ }}
+ resolveBenchDialog={() => {}} // Non-form view is read-only
+ />
+
+ );
+ })
+ ) : (
+
+
+
+
No outline available for this parameter
+
+ Content will be added during the accreditation process
+
+
+
+ )}
+
+
+
);
})}
diff --git a/resources/js/pages/auth/forgot-password.tsx b/resources/js/pages/auth/forgot-password.tsx
index 82e171691..7a8dbd2ac 100644
--- a/resources/js/pages/auth/forgot-password.tsx
+++ b/resources/js/pages/auth/forgot-password.tsx
@@ -53,7 +53,7 @@ export default function ForgotPassword({ status }: { status?: string }) {
-
+
Or, return to
log in
diff --git a/resources/js/pages/auth/login.tsx b/resources/js/pages/auth/login.tsx
index 41076e1b1..f99546a8b 100644
--- a/resources/js/pages/auth/login.tsx
+++ b/resources/js/pages/auth/login.tsx
@@ -125,9 +125,9 @@ export default function Login({ status, canResetPassword }: LoginPageProps) {
>
-
{status && (
-
- {status}
-
+
)}
);
diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx
index a2db1aed4..2edd04b83 100644
--- a/resources/js/pages/dashboard.tsx
+++ b/resources/js/pages/dashboard.tsx
@@ -2,7 +2,6 @@ import AppLayout from '@/layouts/app-layout';
import { SharedData, User, type BreadcrumbItem } from '@/types';
import { Head, usePage } from '@inertiajs/react';
import { DataTable } from '@/components/charts/data-table';
-import GuideTour from "@/pages/test/GuideTour";
import { type ActivityLogs, type DocumentStatistics, type FrequencyUploads, type OverallUploads } from '@/types/dashboard';
import { columns } from '@/components/charts/data-table-columns/logs';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
@@ -138,7 +137,6 @@ export default function Dashboard({ frequencyUploads, documentStatistics, overal
documentStatistics={activeStatistics}
/>
-
diff --git a/resources/js/pages/document/ratings.tsx b/resources/js/pages/document/ratings.tsx
index 12ab9e370..0429b939b 100644
--- a/resources/js/pages/document/ratings.tsx
+++ b/resources/js/pages/document/ratings.tsx
@@ -204,7 +204,6 @@ export default function Ratings() {
const program = programs.find((p) => p.id === programId);
if (!program) return;
- console.log(`Exporting "${program.program_name}" as ${type.toUpperCase()}`);
alert(` Exporting ${program.program_name} as ${type.toUpperCase()}`);
@@ -228,7 +227,6 @@ export default function Ratings() {
const area = programs.flatMap((p) => p.assigned_areas).find((a) => a.id === areaId);
if (!area) return;
- console.log(`Exporting "${area.area_name}" as ${type.toUpperCase()}`);
alert(`Exporting ${area.area_name} as ${type.toUpperCase()}`);
setExportAreaDropdown((prev) => ({ ...prev, [areaId]: false }));
diff --git a/resources/js/pages/manage-programs.tsx b/resources/js/pages/manage-programs.tsx
index f95e6e2e8..2dbe1c575 100644
--- a/resources/js/pages/manage-programs.tsx
+++ b/resources/js/pages/manage-programs.tsx
@@ -10,7 +10,8 @@ import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, Di
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import AppLayout from '@/layouts/app-layout';
import type { BreadcrumbItem, PerProgramUnderSurvey } from '@/types';
-import { Head, router, usePage, usePoll } from '@inertiajs/react';
+import { Head, router, usePage } from '@inertiajs/react';
+import { useSmartPoll } from '@/hooks/use-smart-poll';
import { Archive, BookCheck, Edit, FilePlus, Folders, GraduationCap, NotebookIcon, PlusCircleIcon, ScrollText } from 'lucide-react';
import { PageTitle } from '@/components/page-header';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
@@ -32,7 +33,7 @@ export default function ManagePrograms({ programs }: ProgramsProps) {
const role = user.roles.role_name;
const assignedPrograms = auth.programs;
- usePoll(5000);
+ useSmartPoll(5000);
const [activeTab, setActiveTab] = useState('active');
const [searchTerm, setSearchTerm] = useState('');
diff --git a/resources/js/pages/others.tsx b/resources/js/pages/others.tsx
index 597b58a46..c5f31bac1 100644
--- a/resources/js/pages/others.tsx
+++ b/resources/js/pages/others.tsx
@@ -1,7 +1,8 @@
import PageHeader from '@/components/guest-page-header';
import Layout from '@/layouts/landing-layout';
import { ContentPages, OtherServices } from '@/types/content';
-import { Head, usePoll } from '@inertiajs/react';
+import { Head } from '@inertiajs/react';
+import { useSmartPoll } from '@/hooks/use-smart-poll';
import { Construction, Link } from 'lucide-react';
interface OtherServicesProps {
@@ -10,7 +11,7 @@ interface OtherServicesProps {
}
export default function Others({ page, others }: OtherServicesProps) {
- usePoll(5000);
+ useSmartPoll(5000);
const EmptyState = ({
title,
diff --git a/resources/js/pages/programs.tsx b/resources/js/pages/programs.tsx
index 7308bf0d8..5cd4d9946 100644
--- a/resources/js/pages/programs.tsx
+++ b/resources/js/pages/programs.tsx
@@ -1,7 +1,8 @@
import PageHeader from '@/components/guest-page-header';
import Layout from '@/layouts/landing-layout';
import type { ProgramsUnderSurvey } from '@/types';
-import { Head, Link, usePage, usePoll } from '@inertiajs/react';
+import { Head, Link, usePage } from '@inertiajs/react';
+import { useSmartPoll } from '@/hooks/use-smart-poll';
import { Construction, ImageOff } from 'lucide-react';
interface ProgramsProps {
@@ -12,7 +13,7 @@ export default function Programs({ programs }: ProgramsProps) {
const { auth } = usePage
().props;
const user = auth.user;
- usePoll(5000);
+ useSmartPoll(5000);
const EmptyState = ({ title, description }: { title: string; description: string }) => (
diff --git a/resources/js/pages/programview.tsx b/resources/js/pages/programview.tsx
index ba6b1b6db..bec31b1b3 100644
--- a/resources/js/pages/programview.tsx
+++ b/resources/js/pages/programview.tsx
@@ -5,7 +5,8 @@ import { Button } from '@/components/ui/button';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
import Layout from '@/layouts/landing-layout';
import { PerProgramUnderSurvey } from '@/types';
-import { Head, router, usePage, usePoll, useRemember } from '@inertiajs/react';
+import { Head, router, usePage, useRemember } from '@inertiajs/react';
+import { useSmartPoll } from '@/hooks/use-smart-poll';
import {
AlertCircle,
BookOpen,
@@ -148,8 +149,27 @@ const FacultyCard = forwardRef
(({ faculty, isL
FacultyCard.displayName = 'FacultyCard';
+const FacultyCardWrapper = ({ f, i, facultyLoading }: { f: any; i: number; facultyLoading: boolean }) => {
+ const [cardRef, cardInView] = useInView(0.2);
+ return (
+
+ );
+};
+
export default function Programs({ program }: PerProgramProps) {
- usePoll(5000);
+ useSmartPoll(5000);
const [level, setLevel] = useRemember(program.levels[0]?.level, 'level');
const [loading, setLoading] = useState(false);
@@ -425,27 +445,14 @@ export default function Programs({ program }: PerProgramProps) {
/>
) : (
- {program.faculty_staff?.map((f, i) => {
- const [cardRef, cardInView] = useInView(0.2);
- return (
-
-
- );
- })}
+ {program.faculty_staff?.map((f, i) => (
+
+ ))}
)}
diff --git a/resources/js/pages/settings/archive.tsx b/resources/js/pages/settings/archive.tsx
index d2b085d36..7bd816fc0 100644
--- a/resources/js/pages/settings/archive.tsx
+++ b/resources/js/pages/settings/archive.tsx
@@ -176,12 +176,10 @@ export default function ArchiveComponent() {
}
const handleRestore = () => {
- console.log("Restoring items:", selectedItems)
setSelectedItems([])
}
const handlePermanentDelete = () => {
- console.log("Permanently deleting items:", selectedItems)
setSelectedItems([])
}
diff --git a/resources/js/pages/test/GuideTour.tsx b/resources/js/pages/test/GuideTour.tsx
deleted file mode 100644
index 9fb28e7b0..000000000
--- a/resources/js/pages/test/GuideTour.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-"use client";
-
-import { useEffect, useRef } from "react";
-import Shepherd from "shepherd.js";
-import "shepherd.js/dist/css/shepherd.css";
-import "@/pages/test/shepherd-custom.scss";
-import { usePage } from "@inertiajs/react";
-
-
-
-
-const GuideTour = () => {
- const { url } = usePage();
- const tourRef = useRef(null);
-
- useEffect(() => {
- const tour = new Shepherd.Tour({
- useModalOverlay: true,
- defaultStepOptions: {
- // UI/UX Improvement: Enable the close icon for easy exit
- // cancelIcon: { enabled: true },
- classes: "shepherd-theme-custom",
- scrollTo: { behavior: "smooth", block: "center" },
- arrow: false, // You may enable this if you need the arrow
- },
- });
-
- tourRef.current = tour;
-
- // --- Dashboard steps ---
- if (url === "/dashboard") {
- tour.addStep({
- id: "d1",
- title: "Welcome to the Dashboard!",
- text: "This tour will show you the key areas of the dashboard. Click 'Start tour' to begin, or press F1 anytime to open and esc to exit.",
- attachTo: { element: ".dashboard-title", on: "bottom" },
- buttons: [
- {
- text: "Start tour",
- action: tour.next,
- },
- ],
- });
-
- tour.addStep({
- id: "d2",
- title: "Stats Overview",
- text: "Your current performance metrics are summarized here. Keep an eye on these key indicators!",
- attachTo: { element: "#stats-card", on: "right" },
- buttons: [
- {
- text: "Next",
- action: tour.next,
- },
- ],
- });
-
- tour.addStep({
- id: "d3",
- title: "Document Activity Trend",
- text: "A line graph timeline where you can track the frequency of document uploads.",
- attachTo: { element: "#stats-card-left", on: "right" },
- buttons: [
- {
- text: "Next",
- action: tour.next,
- },
- ],
- });
-
- tour.addStep({
- id: "d4",
- title: "Document Activity Trend",
- text: "A line graph timeline where you can track the frequency of document uploads.",
- attachTo: { element: "#stats-card-center", on: "right" },
- buttons: [
- {
- text: "Next",
- action: tour.next,
- },
- ],
- });
-
- tour.addStep({
- id: "d5",
- title: "Document Activity Trend",
- text: "A line graph timeline where you can track the frequency of document uploads.",
- attachTo: { element: "#stats-card-right", on: "right" },
- buttons: [
- {
- text: "Next",
- action: tour.next,
- },
- ],
- });
-
- tour.addStep({
- id: "d6",
- title: "Document Activity Trend",
- text: "A line graph timeline where you can track the frequency of document uploads.",
- attachTo: { element: "#stat-table", on: "right" },
- buttons: [
- {
- text: "Finish",
- action: tour.complete,
- },
- ],
- });
- }
-
- // --- Programs page steps ---
- if (url === "/users") {
- tour.addStep({
- id: "p1",
- title: "Welcome to User Management Page!",
- text: "This page will handle the account creations of users within the system",
- attachTo: { element: "", on: "bottom" },
- buttons: [
- {
- text: "Next",
- action: tour.next,
- },
- ],
- });
-
- tour.addStep({
- id: "p2",
- title: "Create Program",
- text: "Click this button to quickly add a new program and define its core parameters.",
- attachTo: { element: "#user-table", on: "bottom" },
- buttons: [
- {
- text: "Finish",
- action: tour.complete,
- },
- ],
- });
- }
-
- // --- F1 keyboard activation ---
- const handleKey = (e: KeyboardEvent) => {
- // Note: The key is "F1" (case-sensitive for e.key)
- if (e.key === "F1") {
- e.preventDefault();
- // Only start if the tour is not already active
- if (!tour.isActive()) tour.start();
- }
- };
-
- window.addEventListener("keydown", handleKey);
- return () => window.removeEventListener("keydown", handleKey);
- }, [url]);
-
- return null; // no extra button needed; F1 triggers it
-};
-
-export default GuideTour;
\ No newline at end of file
diff --git a/resources/js/pages/test/shepherd-custom.scss b/resources/js/pages/test/shepherd-custom.scss
deleted file mode 100644
index 6faf98a70..000000000
--- a/resources/js/pages/test/shepherd-custom.scss
+++ /dev/null
@@ -1,160 +0,0 @@
-/* Shepherd.js Full Customization Template */
-
-// --- Variables (Use SCSS variables for easy theming!) ---
-
-// 1. COLORS
-$step-bg-color: #ffffff;
-$step-text-color: #1f2937;
-$step-muted-text: #6b7280;
-$step-shadow-color: rgba(0, 0, 0, 0.1);
-
-$primary-btn-bg: #b91c1c; // Example Red
-$primary-btn-text: #ffffff;
-$secondary-btn-bg: transparent;
-
-// 2. DIMENSIONS
-$border-radius: 0.75rem;
-$padding: 1.5rem;
-$btn-radius: 0.5rem;
-$arrow-size: 10px;
-
-.shepherd-theme-custom .shepherd-target {
- border-radius: 12px; // rounded corners around highlighted element
- box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.5); // blue ring
- transition: box-shadow 0.3s;
-}
-
-// --- MODAL OVERLAY ---
-// This covers the entire screen behind the step.
-.shepherd-modal-overlay-container {
- // Required to place the tour step behind other elements (e.g., header z-index: 1000)
- z-index: 998 !important;
-
- .shepherd-modal-overlay {
- opacity: 0.75 !important; // Adjust opacity
- background-color: #000000 !important; // Dark background color
- }
-}
-
-// --- STEP CONTAINER (.shepherd-theme-custom) ---
-.shepherd-theme-custom {
- // Required to place the tour step behind other elements
- z-index: 999 !important;
-
- box-shadow:
- 0 4px 6px -1px $step-shadow-color,12
- 0 2px 4px -2px $step-shadow-color !important;
- border-radius: $border-radius !important;
-
- // --- Content Wrapper (Holds background/padding) ---
- .shepherd-content {
- background-color: $step-bg-color !important;
- border-radius: $border-radius !important;
- padding: 0 !important;
- }
-
- // --- 1. HEADER (Title and Close Icon) ---
- .shepherd-header {
- // Layout
- display: flex !important;
- justify-content: space-between !important;
- align-items: center !important;
- border-radius: $border-radius $border-radius 0 0 !important;
- background-color: #ffffff00 !important;
- // Padding (Top, Right, Bottom, Left)
- padding: $padding $padding 0.75rem $padding !important;
- }
-
- // --- Title Text ---
- .shepherd-title {
- color: $step-text-color !important;
- font-weight: 600 !important;
- font-size: 1rem !important;
- margin: 0 !important;
- }
-
- // --- Cancel/Close Icon ---
- .shepherd-cancel-icon {
- color: $step-muted-text !important;
- transition: color 0.2s ease !important;
-
- &:hover {
- color: $step-text-color !important;
- }
- }
-
- // --- 2. BODY TEXT ---
- .shepherd-text {
- color: $step-muted-text !important;
- font-size: 0.9rem !important;
- line-height: 1.6 !important;
-
- // Padding (Top, Right, Bottom, Left)
- padding: 0.75rem $padding $padding $padding !important;
- border-top: 1px solid !important;
- }
-
- // --- 3. FOOTER (Buttons) ---
- .shepherd-footer {
- display: flex !important;
- justify-content: flex-end !important;
- gap: 0.75rem !important;
-
- // Red Footer Background (Matching your design)
- background-color: $primary-btn-bg !important;
- padding: 0.75rem $padding $padding $padding !important;
- border-bottom-left-radius: $border-radius !important;
- border-bottom-right-radius: $border-radius !important;
- border-top: none !important;
-
- // --- Buttons (Targeted by Shepherd classes) ---
- .shepherd-button {
- padding: 2 !important;
- border-radius: $btn-radius !important;
- font-weight: 600 !important;
- border: none !important;
- transition: all 0.2s ease !important;
-
-
-
- // Primary Button: White Fill on Red Footer
- &.shepherd-button-primary {
- background-color: #612828 !important;
- color: $primary-btn-bg !important;
- }
-
- // Secondary Button (e.g., 'Back' or 'Cancel' button)
- &.shepherd-button-secondary {
- background-color: transparent !important;
- color: #ffffff !important;
- border: 1px solid #ffffff !important;
- }
-
- // Fallback for first button (if not primary)
- &:nth-child(1):not(.shepherd-button-primary) {
- background-color: transparent !important;
- color: #ffffff !important;
- border: 1px solid #ffffff !important;
- }
- }
- }
-
- // --- 4. ARROW/TOOLTIP POINTER ---
- .shepherd-arrow,
- .shepherd-arrow:before {
- background: #b91c1c !important; // Arrow matches white body
- box-shadow:
- 0 4px 6px -1px black ,12
- 0 2px 4px -2px black !important;
- }
-
-
-
- // // Remove arrow box-shadow for simple look
- // &[data-popper-placement^='top'] .shepherd-arrow:before,
- // &[data-popper-placement^='bottom'] .shepherd-arrow:before,
- // &[data-popper-placement^='left'] .shepherd-arrow:before,
- // &[data-popper-placement^='right'] .shepherd-arrow:before {
- // box-shadow: none !important;
- // }
-}
diff --git a/resources/js/pages/user-management.tsx b/resources/js/pages/user-management.tsx
index f12624c77..1aaa5a3f3 100644
--- a/resources/js/pages/user-management.tsx
+++ b/resources/js/pages/user-management.tsx
@@ -8,8 +8,7 @@ import { AssignablePrograms, AssignableRoles, type UserRecords } from '@/types/u
import { Head } from '@inertiajs/react';
import { SquareUserIcon, User2, User2Icon, UserPlus } from 'lucide-react';
import { useState } from 'react';
-import GuideTour from "@/pages/test/GuideTour";
-import { ActionCard, PageTitle } from '@/components/page-header';
+import { PageTitle } from '@/components/page-header';
interface UsersProps {
@@ -55,7 +54,6 @@ export default function Users({ userRecords, programRoles, roles }: UsersProps)
return (
<>
-
diff --git a/resources/js/pages/welcome.tsx b/resources/js/pages/welcome.tsx
index a8dadc695..88e9b0dc5 100644
--- a/resources/js/pages/welcome.tsx
+++ b/resources/js/pages/welcome.tsx
@@ -15,7 +15,6 @@ const Link = ({ href, children, ...props }: { href: string; children: React.Reac
const router = {
visit: (url: string, options: any) => {
- console.log(`Mock Router: visit "${url}" with options:`, options);
},
};
@@ -456,8 +455,6 @@ export default function Welcome({ page, carousel_images }: LandingProps) {
}, []);
const carousel_paths = carousel_images.map((img) => img.image_path);
- // console.log(carousel_images[0].image_path);
- // console.log(carousel_images);
return (
<>
diff --git a/resources/js/types/files.ts b/resources/js/types/files.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/resources/js/types/guest.ts b/resources/js/types/guest.ts
deleted file mode 100644
index e69de29bb..000000000
diff --git a/resources/js/types/index.ts b/resources/js/types/index.ts
index 753ecfc64..c11183588 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 {
@@ -31,56 +32,8 @@ export interface SharedData {
}
export interface GuestNavigation {
- programs: NavPrograms[];
+ programs: Program[];
outlines?: ParameterOutlines[];
- [key: string]: unknown; // This allows for additional properties...
-}
-
-export interface NavPrograms {
- program_id: number;
- program_name: string;
- program_link: string;
- [key: string]: unknown; // This allows for additional properties...
-}
-
-//Redundant
-export interface ProgramsUnderSurvey {
- program_id: number;
- program_name: string;
- degree_type: string;
- program_description: string;
- program_image_name: string;
- program_image_path: string;
- program_link: string;
- levels?: AccreditationLevels;
- active_levels?: AccreditationLevels;
- [key: string]: unknown; // This allows for additional properties...
-}
-
-//Redundant
-export interface PerProgramUnderSurvey {
- program_id: number;
- degree_type: string;
- program_name: string;
- program_link: string;
- program_description: string;
- under_survey: boolean;
- program_image_name: string;
- program_image_path: string;
- is_active: boolean;
- color?: string;
- levels?: AccreditationLevels[];
- latest_level?: AccreditationLevels;
- faculty_staff?: FacultyStaff[];
- objectives: ProgramObjectives[];
- gallery: ProgramGalleryImages[];
- [key: string]: unknown;
-}
-
-export interface ProgramPrivilege {
- program_name: string;
- program_link: string;
- latest_level?: AccreditationLevels;
[key: string]: unknown;
}
@@ -94,32 +47,44 @@ export interface User {
created_at: string;
updated_at: string;
roles?: Roles;
- [key: string]: unknown; // This allows for additional properties...
+ [key: string]: unknown;
}
export interface Roles {
role_id: number;
role_name: string;
- [key: string]: unknown; // This allows for additional properties...
+ [key: string]: unknown;
}
-//Redundant
-export interface PerProgram {
+export interface Program {
program_id: number;
degree_type: string;
program_name: string;
program_link: string;
- program_description: string;
- under_survey: boolean;
- program_image_name: string;
- program_image_path: string;
- levels?: AccreditationLevels[];
+ program_description?: string;
+ program_image_name?: string;
+ program_image_path?: string;
+ under_survey?: boolean;
+ is_active?: boolean;
+ color?: string;
+ // Handle the ambiguity: allow both single object and array for flexibility during transition
+ // but ideally we should move towards a consistent one.
+ levels?: AccreditationLevels | AccreditationLevels[];
+ active_levels?: AccreditationLevels | AccreditationLevels[];
+ latest_level?: AccreditationLevels;
faculty_staff?: FacultyStaff[];
objectives?: ProgramObjectives[];
gallery?: ProgramGalleryImages[];
[key: string]: unknown;
}
+export interface ProgramPrivilege {
+ program_name: string;
+ program_link: string;
+ latest_level?: AccreditationLevels;
+ [key: string]: unknown;
+}
+
export interface ProgramObjectives {
program_objective_id: number;
program_id: number;
@@ -151,7 +116,6 @@ export interface AccreditationLevels {
[key: string]: unknown;
}
-//Redundand
export interface ProgramAreas {
area_id: number;
program_id: number;
@@ -168,17 +132,6 @@ export interface ProgramAreas {
[key: string]: unknown;
}
-//Redundant
-export interface Program {
- program_id: number;
- degree_type: string;
- program_name: string;
- program_link: string;
- levels?: AccreditationLevels;
- [key: string]: unknown; // This allows for additional properties...
-}
-
-//Redundant
export interface Area {
area_id: number;
program_id: number;
@@ -192,7 +145,7 @@ export interface Area {
areaParameters?: AreaParameters[];
areaForms?: AreaForms[];
levels?: AccreditationLevels;
- [key: string]: unknown; // This allows for additional properties...
+ [key: string]: unknown;
}
export interface AreaParameters {
@@ -202,7 +155,7 @@ export interface AreaParameters {
parameter_description: string;
area?: Area;
parameter_outlines?: ParameterOutlines[];
- [key: string]: unknown; // This allows for additional properties...
+ [key: string]: unknown;
}
export interface ParameterOutlines {
@@ -216,7 +169,7 @@ export interface ParameterOutlines {
area_parameters?: AreaParameters;
paramter_outline_category?: ParameterOutlineCategory;
area_files?: AreaFiles;
- [key: string]: unknown; // This allows for additional properties...
+ [key: string]: unknown;
}
export interface ParameterOutlineCategory {
@@ -224,7 +177,7 @@ export interface ParameterOutlineCategory {
category_name: string;
category_description: string;
parameter_outlines?: ParameterOutlines[];
- [key: string]: unknown; // This allows for additional properties...
+ [key: string]: unknown;
}
export interface AreaFiles {
@@ -236,7 +189,7 @@ export interface AreaFiles {
file_rejection_reason: string | null;
parameter_outlines?: ParameterOutlines;
file_status?: FileStatus;
- [key: string]: unknown; // This allows for additional properties...
+ [key: string]: unknown;
}
export interface FileStatus {
@@ -244,7 +197,7 @@ export interface FileStatus {
status_name: string;
area_files?: AreaFiles[];
areaForms?: AreaForms[];
- [key: string]: unknown; // This allows for additional properties...
+ [key: string]: unknown;
}
export interface AreaForms {
@@ -260,14 +213,14 @@ export interface AreaForms {
area_form_category?: AreaFormCategory;
area?: Area;
file_status?: FileStatus;
- [key: string]: unknown; // This allows for additional properties...
+ [key: string]: unknown;
}
export interface AreaFormCategory {
area_form_category_id: number;
category_name: string;
areaForms?: AreaForms[];
- [key: string]: unknown; // This allows for additional properties...
+ [key: string]: unknown;
}
export interface FilesOverview {
diff --git a/resources/js/types/toast.ts b/resources/js/types/toast.ts
deleted file mode 100644
index 9caca6a58..000000000
--- a/resources/js/types/toast.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export interface NotificationContent {
- type: string;
- title: string;
- message: string;
-}
-
-export interface NotifProps {
- flash?: NotificationContent;
-}
diff --git a/resources/js/workers/pdf-compressor.worker.ts b/resources/js/workers/pdf-compressor.worker.ts
new file mode 100644
index 000000000..51d104e82
--- /dev/null
+++ b/resources/js/workers/pdf-compressor.worker.ts
@@ -0,0 +1,177 @@
+/**
+ * PDF Compressor Web Worker
+ *
+ * Performance priorities:
+ * - Runs entirely off the main thread (zero UI jank)
+ * - Pages rendered in parallel batches via Promise.all
+ * - ArrayBuffer ownership transferred (zero-copy) between threads
+ * - OffscreenCanvas used for GPU-accelerated rendering
+ * - Files under threshold are passed through untouched
+ */
+
+import * as pdfjs from 'pdfjs-dist';
+import { PDFDocument } from 'pdf-lib';
+
+// ── When running inside a Worker, pdfjs must NOT spawn its own child worker.
+// Setting an empty string tells pdfjs to run synchronously in this thread.
+pdfjs.GlobalWorkerOptions.workerSrc = '';
+
+// ── Message contract ──────────────────────────────────────────────────────────
+
+interface CompressRequest {
+ type: 'compress';
+ buffer: ArrayBuffer;
+ quality: number; // JPEG quality 0-1
+ scale: number; // render resolution scale
+ skipThresholdBytes: number;
+}
+
+interface ProgressMessage {
+ type: 'progress';
+ page: number;
+ total: number;
+}
+
+interface CompleteMessage {
+ type: 'complete';
+ buffer: ArrayBuffer;
+ originalSize: number;
+ compressedSize: number;
+ skipped: boolean;
+}
+
+interface ErrorMessage {
+ type: 'error';
+ message: string;
+}
+
+// ── Page rendering ────────────────────────────────────────────────────────────
+
+async function renderPageToJpeg(
+ page: pdfjs.PDFPageProxy,
+ scale: number,
+ quality: number,
+): Promise
{
+ const viewport = page.getViewport({ scale });
+ const canvas = new OffscreenCanvas(
+ Math.round(viewport.width),
+ Math.round(viewport.height),
+ );
+
+ const ctx = canvas.getContext('2d');
+ if (!ctx) throw new Error('Could not get 2D context from OffscreenCanvas');
+
+ // Render PDF page onto canvas
+ await page.render({
+ canvasContext: ctx as unknown as CanvasRenderingContext2D,
+ canvas: canvas as unknown as HTMLCanvasElement,
+ viewport,
+ }).promise;
+
+ // Export as JPEG blob (GPU-accelerated in supporting browsers)
+ const blob = await canvas.convertToBlob({ type: 'image/jpeg', quality });
+ return new Uint8Array(await blob.arrayBuffer());
+}
+
+// ── Main handler ──────────────────────────────────────────────────────────────
+
+self.onmessage = async (event: MessageEvent) => {
+ const { buffer, quality, scale, skipThresholdBytes } = event.data;
+ const originalSize = buffer.byteLength;
+
+ // ── Fast-path: skip small files ──────────────────────────────────────────
+ if (originalSize < skipThresholdBytes) {
+ const msg: CompleteMessage = {
+ type: 'complete',
+ buffer,
+ originalSize,
+ compressedSize: originalSize,
+ skipped: true,
+ };
+ // Transfer ownership back — zero copy
+ (self as unknown as Worker).postMessage(msg, [buffer]);
+ return;
+ }
+
+ try {
+ // ── Load source PDF ──────────────────────────────────────────────────
+ const sourceBytes = new Uint8Array(buffer);
+ const pdf = await pdfjs.getDocument({ data: sourceBytes }).promise;
+ const numPages = pdf.numPages;
+
+ // ── Render all pages in parallel batches of 4 ───────────────────────
+ // Batching avoids holding too many canvases in memory simultaneously.
+ const BATCH_SIZE = 4;
+ const pageJpegs: Uint8Array[] = new Array(numPages);
+ let pagesCompleted = 0;
+
+ for (let batchStart = 1; batchStart <= numPages; batchStart += BATCH_SIZE) {
+ const batchEnd = Math.min(batchStart + BATCH_SIZE - 1, numPages);
+ const batchIndices = Array.from(
+ { length: batchEnd - batchStart + 1 },
+ (_, i) => batchStart + i,
+ );
+
+ const batchResults = await Promise.all(
+ batchIndices.map(async (pageNum) => {
+ const page = await pdf.getPage(pageNum);
+ const jpeg = await renderPageToJpeg(page, scale, quality);
+ page.cleanup(); // Free pdfjs internal resources immediately
+ return { pageNum, jpeg };
+ }),
+ );
+
+ for (const { pageNum, jpeg } of batchResults) {
+ pageJpegs[pageNum - 1] = jpeg;
+ pagesCompleted++;
+
+ const progress: ProgressMessage = {
+ type: 'progress',
+ page: pagesCompleted,
+ total: numPages,
+ };
+ (self as unknown as Worker).postMessage(progress);
+ }
+ }
+
+ // ── Assemble compressed PDF from JPEG images ─────────────────────────
+ const outputPdf = await PDFDocument.create();
+
+ for (let i = 0; i < numPages; i++) {
+ const page = await pdf.getPage(i + 1);
+ const viewport = page.getViewport({ scale });
+ const jpgImage = await outputPdf.embedJpg(pageJpegs[i]);
+ const pdfPage = outputPdf.addPage([viewport.width, viewport.height]);
+ pdfPage.drawImage(jpgImage, {
+ x: 0,
+ y: 0,
+ width: viewport.width,
+ height: viewport.height,
+ });
+ }
+
+ // ── Save with compression ─────────────────────────────────────────────
+ const compressedBytes = await outputPdf.save({ useObjectStreams: true });
+ const compressedBuffer = compressedBytes.buffer.slice(
+ compressedBytes.byteOffset,
+ compressedBytes.byteOffset + compressedBytes.byteLength,
+ ) as ArrayBuffer;
+
+ const msg: CompleteMessage = {
+ type: 'complete',
+ buffer: compressedBuffer,
+ originalSize,
+ compressedSize: compressedBuffer.byteLength,
+ skipped: false,
+ };
+ (self as unknown as Worker).postMessage(msg, [compressedBuffer]);
+
+ } catch (err) {
+ // On any error send back the original buffer so upload still works
+ const msg: ErrorMessage = {
+ type: 'error',
+ message: err instanceof Error ? err.message : 'Unknown compression error',
+ };
+ (self as unknown as Worker).postMessage(msg);
+ }
+};
diff --git a/resources/views/vendor/mail/html/header.blade.php b/resources/views/vendor/mail/html/header.blade.php
index dd369858e..4ebcb791e 100644
--- a/resources/views/vendor/mail/html/header.blade.php
+++ b/resources/views/vendor/mail/html/header.blade.php
@@ -5,13 +5,13 @@
-
+
|
|
- PUPCON
- PUP San Juan Accreditation System
+ PUPCON
+ PUP San Juan Accreditation System
|
diff --git a/resources/views/vendor/mail/html/themes/default.css b/resources/views/vendor/mail/html/themes/default.css
index 3d3f08a0f..3fb58fb9f 100644
--- a/resources/views/vendor/mail/html/themes/default.css
+++ b/resources/views/vendor/mail/html/themes/default.css
@@ -5,7 +5,7 @@ body,
body *:not(html):not(style):not(br):not(tr):not(code) {
box-sizing: border-box;
font-family:
- -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
+ 'Inter', 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
position: relative;
}
@@ -139,10 +139,11 @@ img {
-premailer-cellspacing: 0;
-premailer-width: 570px;
background-color: #ffffff;
- border-color: #e8e5ef;
- border-radius: 8px;
+ border-color: #e2e8f0;
+ border-radius: 12px;
border-width: 1px;
- box-shadow: 0 2px 8px rgba(127, 20, 20, 0.08);
+ border-style: solid;
+ box-shadow: none;
margin: 0 auto;
padding: 0;
width: 570px;
@@ -231,7 +232,7 @@ img {
.button {
-webkit-text-size-adjust: none;
- border-radius: 6px;
+ border-radius: 8px;
color: #fff;
display: inline-block;
overflow: hidden;
diff --git a/routes/api.php b/routes/api.php
index 826d75921..cd9852d35 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -5,5 +5,5 @@
use App\Http\Controllers\Api\FacebookController;
Route::middleware(['api'])->group(function () {
- Route::get('/facebook-feed', [FacebookController::class, 'feed']);
+ Route::get('/updates', [FacebookController::class, 'feed']);
});
\ No newline at end of file
diff --git a/vite.config.js b/vite.config.js
index 87cb0aed6..3567372d7 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,10 +1,14 @@
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
-import { resolve } from 'node:path';
+import { resolve, dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
import laravel from 'laravel-vite-plugin';
import { defineConfig } from 'vite';
import os from 'os';
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
function getLocalIp() {
const interfaces = os.networkInterfaces();
for (const name in interfaces) {
@@ -40,7 +44,7 @@ export default defineConfig(({ mode }) => ({
}, */
resolve: {
alias: {
- // eslint-disable-next-line no-undef
+
'ziggy-js': resolve(__dirname, 'vendor/tightenco/ziggy'),
},
},
@@ -48,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,
},