convert to TS component eventList + context

This commit is contained in:
p2405951
2026-03-12 10:45:52 +01:00
parent 23219d1ffe
commit e259f5f93f
11 changed files with 238 additions and 105 deletions
-3
View File
@@ -1,3 +0,0 @@
import { createContext } from "react";
export const EventContext = createContext(null);
+26
View File
@@ -0,0 +1,26 @@
/*import { createContext } from "react";
export const EventContext = createContext(null);*/
import { createContext } from "react";
export type Task = {
name: string;
description: string;
start: string | Date;
end: string | Date;
};
export type EventGroup = {
date: string;
name: string;
start: string | Date;
tasks: Task[];
};
export type EventContextType = {
events: EventGroup[];
};
export const EventContext = createContext<EventContextType | null>(null);
@@ -1,25 +0,0 @@
import styles from "./eventList.module.css";
import { formatDateLetter } from "../../../../utils/date/formatDateLetter.js";
import { useState } from "react";
export default function EventItem({ eventGroup }) {
const [isVisible, setIsVisible] = useState(false);
return <div key={eventGroup.date} className={`${styles.eventGroup} glassBorder`} onClick={() => setIsVisible(!isVisible)}>
<div className={styles.eventTitle}>
<p className={styles.title}>{eventGroup.name}</p>
<p>{formatDateLetter(eventGroup.start)}</p>
</div>
{isVisible &&
<ul className={styles.eventDetails}>
{eventGroup.tasks.map((task, i) => (
<li key={i} className={styles.eventTasks}>
<p>- {task.name} - {task.description}</p>
<p>{formatDateLetter(task.start)} - {formatDateLetter(task.end)}</p>
</li>
))}
</ul>
}
</div>
}
@@ -0,0 +1,80 @@
import styles from "./eventList.module.css";
import { formatDateLetter } from "../../../../utils/date/formatDateLetter";
import { useState } from "react";
type Task = {
name: string;
description: string;
start: string | Date;
end: string | Date;
};
type EventGroup = {
date: string;
name: string;
start: string | Date;
tasks: Task[];
};
type EventItemProps = {
eventGroup: EventGroup;
};
export default function EventItem({ eventGroup }: EventItemProps) {
const [isVisible, setIsVisible] = useState<boolean>(false);
return (
<div
key={eventGroup.date}
className={`${styles.eventGroup} glassBorder`}
onClick={() => setIsVisible(!isVisible)}
>
<div className={styles.eventTitle}>
<p className={styles.title}>{eventGroup.name}</p>
<p>{formatDateLetter(eventGroup.start)}</p>
</div>
{isVisible && (
<ul className={styles.eventDetails}>
{eventGroup.tasks.map((task, i) => (
<li key={i} className={styles.eventTasks}>
<p>
- {task.name} - {task.description}
</p>
<p>
{formatDateLetter(task.start)} - {formatDateLetter(task.end)}
</p>
</li>
))}
</ul>
)}
</div>
);
}
/*import styles from "./eventList.module.css";
import { formatDateLetter } from "../../../../utils/date/formatDateLetter.ts";
import { useState } from "react";
export default function EventItem({ eventGroup }) {
const [isVisible, setIsVisible] = useState(false);
return <div key={eventGroup.date} className={`${styles.eventGroup} glassBorder`} onClick={() => setIsVisible(!isVisible)}>
<div className={styles.eventTitle}>
<p className={styles.title}>{eventGroup.name}</p>
<p>{formatDateLetter(eventGroup.start)}</p>
</div>
{isVisible &&
<ul className={styles.eventDetails}>
{eventGroup.tasks.map((task, i) => (
<li key={i} className={styles.eventTasks}>
<p>- {task.name} - {task.description}</p>
<p>{formatDateLetter(task.start)} - {formatDateLetter(task.end)}</p>
</li>
))}
</ul>
}
</div>
}
*/
@@ -1,35 +0,0 @@
import { useMemo } from "react";
import styles from "./eventList.module.css";
import EventItem from "./eventItem.jsx";
import { EventContext } from "../../../../contexts/events/EventContext.js";
import { useContext } from "react";
function EventList() {
// Utilise une valeur par défaut pour `events` si elle est `undefined`
const { events = [] } = useContext(EventContext);
const sortedEvents = useMemo(() => {
// Vérifie explicitement que `events` est un tableau avant de trier
if (!Array.isArray(events)) return [];
return [...events].sort(
(a, b) => new Date(a.start) - new Date(b.start)
);
}, [events]);
return (
<div className={`${styles.glassCard} glassCard`}>
<h2>Liste des événements à venir</h2>
{sortedEvents.length > 0 ? (
<div className={styles.eventList}>
{sortedEvents.map((eventGroup, index) => (
<EventItem eventGroup={eventGroup} key={index} />
))}
</div>
) : (
<p>Aucun événement planifié pour l'instant.</p>
)}
</div>
);
}
export default EventList;
@@ -0,0 +1,88 @@
/*import { useMemo } from "react";
import styles from "./eventList.module.css";
import EventItem from "./eventItem.jsx";
import { EventContext } from "../../../../contexts/events/EventContext.js";
import { useContext } from "react";
function EventList() {
// Utilise une valeur par défaut pour `events` si elle est `undefined`
const { events = [] } = useContext(EventContext);
const sortedEvents = useMemo(() => {
// Vérifie explicitement que `events` est un tableau avant de trier
if (!Array.isArray(events)) return [];
return [...events].sort(
(a, b) => new Date(a.start) - new Date(b.start)
);
}, [events]);
return (
<div className={`${styles.glassCard} glassCard`}>
<h2>Liste des événements à venir</h2>
{sortedEvents.length > 0 ? (
<div className={styles.eventList}>
{sortedEvents.map((eventGroup, index) => (
<EventItem eventGroup={eventGroup} key={index} />
))}
</div>
) : (
<p>Aucun événement planifié pour l'instant.</p>
)}
</div>
);
}
export default EventList;*/
import { useMemo, useContext } from "react";
import styles from "./eventList.module.css";
import EventItem from "./eventItem";
import { EventContext } from "../../../../contexts/events/EventContext";
type Task = {
name: string;
description: string;
start: string | Date;
end: string | Date;
};
type EventGroup = {
date: string;
name: string;
start: string | Date;
tasks: Task[];
};
type EventContextType = {
events?: EventGroup[];
};
function EventList() {
const { events = [] } = useContext(EventContext) as EventContextType;
const sortedEvents = useMemo<EventGroup[]>(() => {
if (!Array.isArray(events)) return [];
return [...events].sort(
(a, b) =>
new Date(a.start).getTime() - new Date(b.start).getTime()
);
}, [events]);
return (
<div className={`${styles.glassCard} glassCard`}>
<h2>Liste des événements à venir</h2>
{sortedEvents.length > 0 ? (
<div className={styles.eventList}>
{sortedEvents.map((eventGroup, index) => (
<EventItem eventGroup={eventGroup} key={index} />
))}
</div>
) : (
<p>Aucun événement planifié pour l'instant.</p>
)}
</div>
);
}
export default EventList;
+4 -18
View File
@@ -116,24 +116,10 @@ function ProfilePage() {
setSuccess(null); setSuccess(null);
} }
}; };
// Remplacé : handleDeletePicture extrait vers utils/users/deleteProfilePhoto
const handleDeletePicture = async () => { const handleDeletePicture = async () => {
try {
const res = await deleteProfilePhoto(); deleteProfilePhoto();
if (res.status >= 200 && res.status < 300) { setProfilePicture(null);
setProfilePicture(null);
setSuccess("Photo supprimée !");
setError(null);
await update();
} else {
setError(res.data?.message || "Erreur lors de la suppression.");
setSuccess(null);
}
} catch (err) {
setError("Erreur réseau.");
setSuccess(null);
}
}; };
return ( return (
@@ -154,7 +140,7 @@ function ProfilePage() {
Modifier la photo Modifier la photo
</button> </button>
{user?.profile_photo_path && ( {user?.profile_photo_path && (
<button onClick={handleDeletePicture} className={styles.deletePictureButton}> <button onClick={handleDeletePicture} className={styles.editPictureButton}>
Supprimer Supprimer
</button> </button>
)} )}
@@ -3,27 +3,28 @@ import { useNavigate } from "react-router";
import Button from "../../../../components/ui/button/button"; import Button from "../../../../components/ui/button/button";
import User from "../../interfaces/user.interface" import User from "../../interfaces/user.interface"
interface VolunteersCardProps { interface VolunteersCardProps {
user: User; user: User;
} }
export default function VolunteerCard({ user }: VolunteersCardProps) { export default function VolunteerCard({ user }: VolunteersCardProps) {
const navigate = useNavigate();
const handleNavigate = (): void => { navigate(`/profile/${user.id}`) }
const navigate = useNavigate(); const profilePicture = user.profile_photo_path
? `${import.meta.env.VITE_API_URL}/storage/${user.profile_photo_path}`
: "/react.svg";
const handleNavigate = (): void => { navigate(`/profile/${user.id}`) } return (
<div className={`glassCard ${styles.volunteerCard}`}>
return ( <div className={styles.header}>
<div className={`glassCard ${styles.volunteerCard}`}> <img src={profilePicture} alt="profile" className={styles.profilePicture} />
<div className={styles.header}> <p> {user.name} {user.lastname} </p>
<img src={"/react.svg"} alt="profile" className={styles.profilePicture} /> </div>
<p> {user.name} {user.lastname} </p> <div className={styles.content}>
</div> <p> {user.email} </p>
<div className={styles.content}> <Button variant={"primary"} onClick={handleNavigate}>Voir</Button>
<p> {user.email} </p> </div>
<Button variant={"primary"} onClick={handleNavigate}>Voir</Button> </div>
</div> );
</div> }
);
}
@@ -12,5 +12,6 @@ export default interface User {
updated_at: string; updated_at: string;
verified_at: string | null; verified_at: string | null;
validate: number; validate: number;
profile_photo_path?: string | null;
tasks: Task[]; tasks: Task[];
} }
+2 -2
View File
@@ -51,7 +51,7 @@ export function formatDateLetter(d: string | Date | null | undefined): string {
} }
export function formatDateLetterJS(d: string): string { /*export function formatDateLetterJS(d: string): string {
const monthNames = [ const monthNames = [
"Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Janvier", "Février", "Mars", "Avril", "Mai", "Juin",
"Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre" "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"
@@ -61,5 +61,5 @@ export function formatDateLetterJS(d: string): string {
if (!year || !month || !day) return "Date invalide"; if (!year || !month || !day) return "Date invalide";
return `${day} ${monthNames[parseInt(month, 10) - 1]} ${year}`; return `${day} ${monthNames[parseInt(month, 10) - 1]} ${year}`;
} }*/
+18 -4
View File
@@ -1,10 +1,24 @@
import fetchWrapper from "../fetchWrapper"; import getXSRFToken from "../getXSRF.js";
export default async function deleteProfilePhoto(): Promise<{ status: number; ok: boolean; data?: any; error?: string }> { export default async function deleteProfilePhoto(): Promise<{ status: number; ok: boolean; data?: any; error?: string }> {
try { try {
const response = await fetchWrapper("/api/profile-photo", null, "DELETE"); const csrfToken = await getXSRFToken();
return { status: response.status || 0, ok: response.status >= 200 && response.status < 300, data: response.data }; const response = await fetch(`${import.meta.env.VITE_API_URL}/api/profile-photo`, {
method: "DELETE",
headers: { "X-XSRF-TOKEN": decodeURIComponent(csrfToken) },
credentials: "include",
});
const status = response.status;
let data = null;
try {
data = await response.json();
} catch (e) {
// Pas de JSON dans la réponse
}
return { status, ok: response.ok, data };
} catch (err: any) { } catch (err: any) {
return { status: 0, ok: false, error: err?.message || "Network error" }; return { status: 0, ok: false, error: err?.message || "Erreur réseau" };
} }
} }