+ {/* Meta Info */}
+
+
+ {status}
+
+
{category}
+
+
+ {createdAt}
+
+
+
+ {author}
+
+
+
+ {/* Title */}
+
+ {title || "Untitled"}
+
+
+ {/* Subtitle */}
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+
+ {/* Content */}
+
+
No content available...",
+ }}
+ />
+
+
+ {/* Reading Stats */}
+
+
+
);
};
+
+export default ContentPreview;
diff --git a/src/features/content-management/cover-image.tsx b/src/features/content-management/cover-image.tsx
deleted file mode 100644
index 683b907..0000000
--- a/src/features/content-management/cover-image.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import { useState } from "react";
-import { ImagePlus, Upload } from "lucide-react";
-import { Button } from "@/components/ui/button";
-
-interface CoverImageUploaderProps {
- coverImage: string | null;
- onImageUpload: (file: File, url: string) => void;
- onRemoveImage: () => void;
- className?: string;
-}
-
-export const CoverImageUploader = ({
- coverImage,
- onImageUpload,
- onRemoveImage,
- className = "",
-}: CoverImageUploaderProps) => {
- const [showUploadInput, setShowUploadInput] = useState(false);
-
- const handleFileUpload = (event: React.ChangeEvent
) => {
- const file = event.target.files?.[0];
- if (file) {
- const url = URL.createObjectURL(file);
- onImageUpload(file, url);
- setShowUploadInput(false);
- }
- };
-
- const handleCancel = () => {
- setShowUploadInput(false);
- onRemoveImage();
- };
-
- if (coverImage) {
- return (
-
-

-
-
- );
- }
-
- if (showUploadInput) {
- return (
-
-
-
-
-
-
-
-
-
- );
- }
-
- return (
-
- );
-};
diff --git a/src/features/content-management/image-uploader.tsx b/src/features/content-management/image-uploader.tsx
deleted file mode 100644
index a54b5a8..0000000
--- a/src/features/content-management/image-uploader.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import { useState, useRef } from "react";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { Upload, Link2, X } from "lucide-react";
-import { toast } from "sonner";
-
-interface ImageUploaderProps {
- onImageUpload: (url: string) => void;
- onClose: () => void;
-}
-
-export const ImageUploader = ({ onImageUpload, onClose }: ImageUploaderProps) => {
- const [imageUrl, setImageUrl] = useState("");
- const [isUploading, setIsUploading] = useState(false);
- const fileInputRef = useRef(null);
-
- const handleFileUpload = async (event: React.ChangeEvent) => {
- const file = event.target.files?.[0];
- if (!file) return;
-
- // Validate file type
- if (!file.type.startsWith("image/")) {
- toast.error("Please select an image file");
- return;
- }
-
- // Validate file size (5MB limit)
- if (file.size > 5 * 1024 * 1024) {
- toast.error("Image size must be less than 5MB");
- return;
- }
-
- setIsUploading(true);
- try {
- // Create object URL for preview (in real app, upload to server)
- const url = URL.createObjectURL(file);
- onImageUpload(url);
- toast.success("Image uploaded successfully!");
- onClose();
- } catch (error) {
- toast.error("Failed to upload image");
- } finally {
- setIsUploading(false);
- }
- };
-
- const handleUrlSubmit = () => {
- if (!imageUrl.trim()) {
- toast.error("Please enter an image URL");
- return;
- }
-
- // Basic URL validation
- try {
- new URL(imageUrl);
- onImageUpload(imageUrl);
- toast.success("Image added successfully!");
- onClose();
- } catch {
- toast.error("Please enter a valid URL");
- }
- };
-
- return (
-
-
-
Add Image
-
-
-
- {/* URL Input */}
-
-
-
- setImageUrl(e.target.value)}
- onKeyDown={(e) => e.key === "Enter" && handleUrlSubmit()}
- />
-
-
-
-
-
-
- {/* File Upload */}
-
-
-
fileInputRef.current?.click()}
- >
-
-
- Click to upload or drag and drop
-
-
- PNG, JPG, WEBP up to 5MB
-
-
-
-
-
- {isUploading && (
-
- Uploading image...
-
- )}
-
- );
-};
diff --git a/src/features/content-management/index.tsx b/src/features/content-management/index.tsx
new file mode 100644
index 0000000..fae3bf6
--- /dev/null
+++ b/src/features/content-management/index.tsx
@@ -0,0 +1,147 @@
+import { ConfirmDialog } from "@/components/confirm-dialog";
+import DateNotAvailable from "@/components/data-not-avaiable";
+import AddOn from "@/components/skeleton/add-on-skeleton";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { FormateDate } from "@/lib/utils";
+import { Contentmanagement } from "@/store/atoms/content-management/content-management";
+import { useContentManagementData } from "@/store/hooks/content-management/content-management";
+import type { ContentManagement } from "@/types";
+import axios from "axios";
+import { Edit, Eye, Loader2, Trash } from "lucide-react";
+import { useState } from "react";
+import { useRecoilRefresher_UNSTABLE } from "recoil";
+import { toast } from "sonner";
+
+const base_api = import.meta.env.VITE_SERVER_BASE_URL;
+
+const Content = () => {
+ const { ContentInfo, error, loading } = useContentManagementData();
+ const [isLoading, setIsLoading] = useState(false);
+
+ const refreshEvent = useRecoilRefresher_UNSTABLE(Contentmanagement);
+
+ async function handleDelete(content: ContentManagement) {
+ try {
+ const response = await axios.delete(
+ `${base_api}/api/v1/delete-article/${content?.id}`,
+ {
+ withCredentials: true,
+ }
+ );
+
+ if (response.status === 200) {
+ toast.success("Article deleted !!");
+ setIsLoading(false);
+ refreshEvent();
+ }
+ } catch (error) {
+ console.log(error);
+ setIsLoading(false);
+ }
+ }
+
+ if (error) {
+ return error
;
+ }
+
+ if (loading) {
+ ;
+ }
+ const contentData: ContentManagement[] = ContentInfo;
+ return (
+
+
+
+ History
+
+
+ Published
+ Draft
+
+
+
+
+
+
+
+
+ Title
+ Category
+ Date
+ Status
+ Author
+ Action
+
+
+
+
+ {contentData?.length > 0 ? (
+ contentData.map((content, index) => (
+
+ {content.title}
+ {content.category}
+ {FormateDate(content?.publishedAt)}
+
+ {" "}
+
+ {content.status}
+
+
+
+ {content?.author?.firstname} {content?.author?.lastname}
+
+
+
+
+
+ handleDelete(content)}
+ destructive
+ trigger={
+
+ }
+ />
+
+
+
+ ))
+ ) : (
+
+ )}
+
+
+
+
+ );
+};
+
+export default Content;
diff --git a/src/features/content-management/subtitle-input.tsx b/src/features/content-management/subtitle-input.tsx
deleted file mode 100644
index dba0f40..0000000
--- a/src/features/content-management/subtitle-input.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { Type } from "lucide-react";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-
-interface SubtitleInputProps {
- value: string;
- onChange: (value: string) => void;
- showInput: boolean;
- onShowInput: (show: boolean) => void;
- className?: string;
-}
-
-export const SubtitleInput = ({
- value,
- onChange,
- showInput,
- onShowInput,
- className = "",
-}: SubtitleInputProps) => {
- const handleBlur = () => {
- if (!value.trim()) {
- onShowInput(false);
- }
- };
-
- if (value || showInput) {
- return (
-
- onChange(e.target.value)}
- placeholder="Add a subtitle..."
- className="text-xl text-muted-foreground border-none px-0 py-1 h-auto placeholder:text-muted-foreground focus-visible:ring-0 focus-visible:ring-offset-0"
- onBlur={handleBlur}
- />
-
- );
- }
-
- return (
-
- );
-};
diff --git a/src/features/content-management/title-input.tsx b/src/features/content-management/title-input.tsx
deleted file mode 100644
index 9759f07..0000000
--- a/src/features/content-management/title-input.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Input } from "@/components/ui/input";
-
-interface TitleInputProps {
- value: string;
- onChange: (value: string) => void;
- placeholder?: string;
- className?: string;
-}
-
-export const TitleInput = ({
- value,
- onChange,
- placeholder = "Untitled",
- className = "",
-}: TitleInputProps) => {
- return (
-
- onChange(e.target.value)}
- placeholder={placeholder}
- className="text-4xl font-bold border-none px-0 py-2 h-auto text-foreground placeholder:text-muted-foreground focus-visible:ring-0 focus-visible:ring-offset-0"
- style={{ fontSize: "2.5rem", lineHeight: "1.2" }}
- />
-
- );
-};
diff --git a/src/features/events/index.tsx b/src/features/events/index.tsx
index 6ed7f0c..cba22f1 100644
--- a/src/features/events/index.tsx
+++ b/src/features/events/index.tsx
@@ -64,7 +64,7 @@ const EventsData = () => {
const eventData: EventData[] = EventInfo;
- // 🔹 Memoized filtered data
+ // Memoized filtered data
const filteredEvents = useMemo(() => {
return eventData?.filter((event) => {
const matchesSearch =
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 87c868b..6337003 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -11,15 +11,21 @@ export function InputHandler(
e: React.ChangeEvent,
setState: React.Dispatch>
) {
- const target = e.target as HTMLInputElement;
- const { name, value, files, type } = target;
-
- setState((prev: any) => ({
- ...prev,
- [name]: type === "file" && files ? files[0] : value,
- }));
+ const { name, type, value } = e.target;
+
+ if (type === "file" && "files" in e.target) {
+ const files = (e.target as HTMLInputElement).files;
+ setState((prev: any) => ({
+ ...prev,
+ [name]: files && files[0] ? files[0] : null,
+ }));
+ } else {
+ setState((prev: any) => ({
+ ...prev,
+ [name]: value,
+ }));
+ }
}
-
// status handler
export function SelectHandler(name: string, value: string, setState: any) {
diff --git a/src/pages/dashboard/academic-personal.tsx b/src/pages/dashboard/academic-personal.tsx
new file mode 100644
index 0000000..b47bf3b
--- /dev/null
+++ b/src/pages/dashboard/academic-personal.tsx
@@ -0,0 +1,108 @@
+import { Users, Mail, Phone, MapPin } from "lucide-react";
+import { Card, CardContent } from "@/components/ui/card";
+import DashboardBanner from "@/components/dashboard-banner";
+
+interface FacultyMembers {
+ name: string;
+ title: string;
+ specialization: string;
+ email: string;
+ phone: string;
+ building: string;
+}
+
+const facultyMembers: FacultyMembers[] = [
+ {
+ name: "Prof. Maria Joseph",
+ title: "Professor of Computer Science",
+ specialization: "Database Systems, Data Mining",
+ email: "m.joseph@myuni.edu",
+ phone: "+30 210 123 4501",
+ building: "Building B, Room 301",
+ },
+ {
+ name: "Prof. Maria Joseph",
+ title: "Professor of Computer Science",
+ specialization: "Database Systems, Data Mining",
+ email: "m.joseph@myuni.edu",
+ phone: "+30 210 123 4501",
+ building: "Building B, Room 301",
+ },
+ {
+ name: "Prof. Maria Joseph",
+ title: "Professor of Computer Science",
+ specialization: "Database Systems, Data Mining",
+ email: "m.joseph@myuni.edu",
+ phone: "+30 210 123 4501",
+ building: "Building B, Room 301",
+ },
+ {
+ name: "Prof. Maria Joseph",
+ title: "Professor of Computer Science",
+ specialization: "Database Systems, Data Mining",
+ email: "m.joseph@myuni.edu",
+ phone: "+30 210 123 4501",
+ building: "Building B, Room 301",
+ },
+];
+
+const AcademicPersonnel = () => {
+ return (
+
+
+
+ {/* Faculty List */}
+
+ {facultyMembers.map((faculty, index) => (
+
+
+
+
+
+
+
+
+
+ {faculty.name}
+
+
+ {faculty.title}
+
+
+
+
+ Specialization
+
+
+ {faculty.specialization}
+
+
+
+
+
+
+
+ {faculty.email}
+
+
+
+
+ {faculty.building}
+
+
+
+
+
+ ))}
+
+
+ );
+};
+
+export default AcademicPersonnel;
diff --git a/src/pages/dashboard/camous-map.tsx b/src/pages/dashboard/camous-map.tsx
new file mode 100644
index 0000000..2560c8c
--- /dev/null
+++ b/src/pages/dashboard/camous-map.tsx
@@ -0,0 +1,121 @@
+import {
+ MapPin,
+ Clock,
+ Phone,
+ Mail,
+ Navigation,
+ PhoneCall,
+} from "lucide-react";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import DashboardBanner from "@/components/dashboard-banner";
+
+interface Locations {
+ name: string;
+ building: string;
+ description: string;
+ hours: string;
+ phone: string;
+ email: string;
+ hall: string;
+}
+
+const locations: Locations[] = [
+ {
+ name: "Library",
+ building: "Building A, Ground Floor",
+ description: "Primary dining facility with daily specials",
+ hours: "07:00 - 22:00",
+ phone: "+30 210 123 4501",
+ email: "restaurant@myuni.edu",
+ hall: "Hall 1",
+ },
+ {
+ name: "Student Groups",
+ building: "Building C, 1st Floor",
+ description: "Registration, financial aid, and student support",
+ hours: "07:00 - 22:00",
+ phone: "+30 210 123 4501",
+ email: "restaurant@myuni.edu",
+ hall: "Hall 1",
+ },
+];
+
+const CampusMap = () => {
+ return (
+
+
+
+ {/* Map Container */}
+
+
+
+
+
+
Google Map
+
+ Interactive campus map would be displayed here
+
+
+
+
+
+
+ {/* Location Cards */}
+
+ {locations.map((location, index) => (
+
+
+
+
+ {location.name}
+
+
+ {location.building}
+
+
+ {location.description}
+
+
+
+
+
+
+ {location.hours}
+
+
+
+
+ {location.email}
+
+
+
+ {location.hall}
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+ );
+};
+
+export default CampusMap;
diff --git a/src/pages/dashboard/content-management.tsx b/src/pages/dashboard/content-management.tsx
index 80b87ca..8ad7006 100644
--- a/src/pages/dashboard/content-management.tsx
+++ b/src/pages/dashboard/content-management.tsx
@@ -1,62 +1,8 @@
-import { Plus, Eye, Edit, Trash2 } from "lucide-react";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Plus } from "lucide-react";
import { Button } from "@/components/ui/button";
-import { Badge } from "@/components/ui/badge";
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from "@/components/ui/table";
-import { Link } from "react-router-dom";
-
-interface ContentData {
- title: string;
- category: string;
- date: string;
- status: string;
- author: string;
-}
-const contentData: ContentData[] = [
- {
- title: "Registration Deadline Extended",
- category: "Academic",
- date: "2023-09-15",
- status: "Published",
- author: "Admin",
- },
- {
- title: "Library Hours Extended for Exam",
- category: "Facilities",
- date: "2023-09-15",
- status: "Published",
- author: "Admin",
- },
- {
- title: "Registration Deadline Extended",
- category: "Events",
- date: "2023-09-15",
- status: "Draft",
- author: "Admin",
- },
- {
- title: "Guest Lecture: AI in Education",
- category: "Maintenance",
- date: "2023-09-15",
- status: "Published",
- author: "Admin",
- },
- {
- title: "Campus Maintenance Schedule",
- category: "Academic",
- date: "2023-09-15",
- status: "Published",
- author: "Admin",
- },
-];
+import { Link } from "react-router-dom";
+import Content from "@/features/content-management";
const ContentManagement = () => {
return (
@@ -70,8 +16,8 @@ const ContentManagement = () => {
Content Management
Create and manage content
-
- Monday, June 9, 2025
+
+ {new Date().toLocaleDateString()}