feat: add attachments

This commit is contained in:
Nathan Lamy 2025-08-19 20:07:32 +02:00
parent a89546c54e
commit 6428126514
4 changed files with 61 additions and 31 deletions

View file

@ -1,16 +1,16 @@
import { FileText, Image, File } from "lucide-react"; import { FileText, Image, File } from "lucide-react";
import type { Attachment } from "~/lib/api";
export default function AttachmentItem({ attachment }: { attachment: string }) { export default function AttachmentItem({ attachment }: { attachment: Attachment }) {
return ( return (
<a <a
// TODO: BAD: hardcoded URL, should be dynamic (environment variable or config) href={"https://bjcolle.fr/" + attachment.path}
href={"https://bjcolle.fr/" + attachment}
target="_blank" target="_blank"
className="flex items-center gap-2 p-3 border rounded-md hover:bg-muted transition-colors cursor-pointer" className="flex items-center gap-2 p-3 border rounded-md hover:bg-muted transition-colors cursor-pointer"
> >
{getIcon(attachment)} {getIcon(attachment.name)}
<span className="font-medium truncate"> <span className="font-medium truncate">
{getName(attachment) || "Sans Nom"} {attachment.name}
</span> </span>
</a> </a>
); );
@ -44,9 +44,3 @@ const getIcon = (attachment: string) => {
return <File className="h-5 w-5 text-gray-500" />; return <File className="h-5 w-5 text-gray-500" />;
} }
}; };
const getName = (attachment: string) => {
const parts = attachment.replace("pj_doc", "").split("_");
const nameParts = parts.slice(2); // remove the first two parts
return nameParts.join("_");
};

View file

@ -2,7 +2,7 @@ import type { Colle, UserPreferences } from "~/lib/api";
import { Link } from "react-router"; import { Link } from "react-router";
import { Card } from "~/components/ui/card"; import { Card } from "~/components/ui/card";
import { User, Star, CalendarDays, MapPin } from "lucide-react"; import { User, Star, CalendarDays, MapPin, Paperclip } from "lucide-react";
import { Badge } from "~/components/ui/badge"; import { Badge } from "~/components/ui/badge";
import { import {
cn, cn,
@ -35,8 +35,11 @@ export default function ColleCard({
// onToggleFavorite(colle.id, newValue); // onToggleFavorite(colle.id, newValue);
// }; // };
const subjectColor = getColorClass(getSubjectColor(colle.subject.name, preferences)); const subjectColor = getColorClass(
getSubjectColor(colle.subject.name, preferences)
);
const subjectEmoji = getSubjectEmoji(colle.subject.name, preferences); const subjectEmoji = getSubjectEmoji(colle.subject.name, preferences);
const attachmentsCount = colle.attachments?.length || 0;
return ( return (
<Link to={`/colles/${colle.id}`}> <Link to={`/colles/${colle.id}`}>
@ -114,13 +117,12 @@ export default function ColleCard({
</Badge> </Badge>
)} )}
</div> </div>
{/* TODO: Attachments */} {attachmentsCount > 0 && (
{/* {colle.attachmentsCount > 0 && (
<div className="flex items-center gap-1 text-muted-foreground"> <div className="flex items-center gap-1 text-muted-foreground">
<Paperclip className="h-3.5 w-3.5" /> <Paperclip className="h-3.5 w-3.5" />
<span className="text-xs">{colle.attachmentsCount}</span> <span className="text-xs">{attachmentsCount}</span>
</div> </div>
)} */} )}
</div> </div>
</div> </div>
</div> </div>

View file

@ -182,7 +182,7 @@ export interface Colle {
bjid?: string; // Optional field bjid?: string; // Optional field
content?: string; // Optional field content?: string; // Optional field
comment?: string; // Optional field comment?: string; // Optional field
attachments?: string[]; // Optional field, array of attachment URLs attachments?: Attachment[]; // Optional field, array of attachment URLs
} }
interface CollePayload { interface CollePayload {
@ -278,6 +278,20 @@ export const useColle = (id: number) => {
}; };
}; };
export const refreshColle = async (id: number) => {
return makePostRequest(
`/colles/${id}/refresh`,
{},
"Échec de la demande de rafraîchissement de la colle",
"POST"
);
};
export interface Attachment {
path: string;
name: string;
}
/** /**
* === SUBJECTS API === * === SUBJECTS API ===
*/ */

View file

@ -25,9 +25,10 @@ import ColleDetailsSkeleton from "~/components/details/skeleton-details";
import AttachmentItem from "~/components/details/attachment"; import AttachmentItem from "~/components/details/attachment";
import Error from "~/components/error"; import Error from "~/components/error";
import { Badge } from "~/components/ui/badge"; import { Badge } from "~/components/ui/badge";
import { AUTH_ERROR, useColle, useUser } from "~/lib/api"; import { AUTH_ERROR, refreshColle, useColle, useUser } from "~/lib/api";
import { toast } from "sonner"; import { toast } from "sonner";
import { formatDate, formatGrade, formatTime, goBack } from "~/lib/utils"; import { formatDate, formatGrade, formatTime, goBack } from "~/lib/utils";
import { useQueryClient } from "@tanstack/react-query";
// TODO: Preferences for subject colors // TODO: Preferences for subject colors
const getSubjectColor = (_: string) => { const getSubjectColor = (_: string) => {
@ -42,10 +43,17 @@ const getSubjectEmoji = (_: string) => {
// TODO: Move all code to components // TODO: Move all code to components
export default function ColleDetailPage() { export default function ColleDetailPage() {
const { user, isLoading: isUserLoading, error: userError } = useUser(); const { user, isLoading: isUserLoading, error: userError } = useUser();
const queryClient = useQueryClient();
const navigate = useNavigate(); const navigate = useNavigate();
const params = useParams<{ colleId: string }>(); const params = useParams<{ colleId: string }>();
const colleId = parseInt(params.colleId!);
if (isNaN(colleId)) {
return <Navigate to="/" />;
}
const { colle, error, isLoading } = useColle(colleId);
const [isReloading, setIsReloading] = useState(false); const [isReloading, setIsReloading] = useState(false);
if (isUserLoading) { if (isUserLoading) {
@ -61,12 +69,6 @@ export default function ColleDetailPage() {
// TODO: Favorite toggle function // TODO: Favorite toggle function
const toggleStar = () => {}; const toggleStar = () => {};
const colleId = parseInt(params.colleId!);
if (isNaN(colleId)) {
return <Navigate to="/" />;
}
const { colle, error, isLoading } = useColle(colleId);
if (error) if (error)
return ( return (
<Error <Error
@ -82,7 +84,26 @@ export default function ColleDetailPage() {
const handleReload = () => { const handleReload = () => {
setIsReloading(true); setIsReloading(true);
// TODO: HARD RELOAD refreshColle(colle.id)
.then(() => {
toast("Les données de cette colle vont être mises à jour", {
icon: "🔄",
description: "Rafraîchissement en cours...",
});
})
.catch((error) => {
console.error("Error refreshing colle:", error);
toast.error("Échec du rafraîchissement de la colle", {
icon: "❌",
description: "Veuillez réessayer plus tard.",
});
})
.finally(() => {
setIsReloading(false);
queryClient.invalidateQueries({
queryKey: ["colle", colle.id],
});
});
}; };
const handleShare = async () => { const handleShare = async () => {
@ -331,12 +352,11 @@ export default function ColleDetailPage() {
</> </>
)} )}
{/* TODO: Attachments */}
{colle.attachments && colle.attachments?.length > 0 && ( {colle.attachments && colle.attachments?.length > 0 && (
<div> <div>
<h3 className="text-lg font-medium mb-3 flex items-center gap-2"> <h3 className="text-lg font-medium mb-3 flex items-center gap-2">
<Paperclip className="h-5 w-5" /> <Paperclip className="h-5 w-5" />
Attachments Pièces jointes
</h3> </h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3"> <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{colle.attachments.map((attachment, index) => ( {colle.attachments.map((attachment, index) => (