refactor: improve type safety by replacing any types with specific interfaces across API services and components.
This commit is contained in:
@@ -73,7 +73,16 @@ const Calendar: React.FC = () => {
|
||||
|
||||
const handleEventClick = (clickInfo: EventClickArg) => {
|
||||
const event = clickInfo.event;
|
||||
setSelectedEvent(event as unknown as CalendarEvent);
|
||||
setSelectedEvent({
|
||||
id: event.id,
|
||||
title: event.title,
|
||||
start: event.startStr,
|
||||
end: event.endStr,
|
||||
extendedProps: {
|
||||
calendar: event.extendedProps.calendar,
|
||||
},
|
||||
} as CalendarEvent);
|
||||
|
||||
setEventTitle(event.title);
|
||||
setEventStartDate(event.start?.toISOString().split("T")[0] || "");
|
||||
setEventEndDate(event.end?.toISOString().split("T")[0] || "");
|
||||
|
||||
@@ -7,9 +7,10 @@ import "react-quill-new/dist/quill.snow.css";
|
||||
const ReactQuillEditor = dynamic(
|
||||
async () => {
|
||||
const { default: RQ } = await import("react-quill-new");
|
||||
return ({ forwardedRef, ...props }: any) => (
|
||||
return ({ forwardedRef, ...props }: { forwardedRef: React.Ref<any>; [key: string]: any }) => (
|
||||
<RQ ref={forwardedRef} {...props} />
|
||||
);
|
||||
|
||||
},
|
||||
{
|
||||
ssr: false,
|
||||
|
||||
@@ -15,10 +15,13 @@ import { IsolatedContent } from "@/components/ui/IsolatedContent";
|
||||
import { apiDeleteHistorianCV } from "@/service/historianService";
|
||||
import { statusConfig } from "@/service/handler";
|
||||
|
||||
import { Application } from "@/interface/historian";
|
||||
import { MediaItem } from "@/components/tables/MediaTable";
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
application: any;
|
||||
application: Application | null;
|
||||
onRefresh: () => void;
|
||||
}
|
||||
|
||||
@@ -42,7 +45,7 @@ export default function ApplicationDetailModal({
|
||||
}
|
||||
}, [isOpen, application]);
|
||||
|
||||
const isImageFile = (file: any) => {
|
||||
const isImageFile = (file: MediaItem) => {
|
||||
const isImageMime = file.mime_type?.startsWith("image/");
|
||||
const isImageExt = /\.(jpg|jpeg|png|webp|gif)$/i.test(file.storage_key);
|
||||
return isImageMime || isImageExt;
|
||||
@@ -51,17 +54,17 @@ export default function ApplicationDetailModal({
|
||||
const mediaList = application?.media || [];
|
||||
const imageMediaOnly = mediaList.filter(isImageFile);
|
||||
|
||||
const imageSlides = imageMediaOnly.map((item: any) => ({
|
||||
const imageSlides = imageMediaOnly.map((item: MediaItem) => ({
|
||||
src: `${URL_MEDIA}${item.storage_key}`,
|
||||
title: item.original_name,
|
||||
description: `Dung lượng: ${(item.size / 1024).toFixed(2)} KB`,
|
||||
}));
|
||||
|
||||
const handleMediaClick = (item: any) => {
|
||||
const handleMediaClick = (item: MediaItem) => {
|
||||
const fileUrl = `${URL_MEDIA}${item.storage_key}`;
|
||||
if (isImageFile(item)) {
|
||||
const photoIndex = imageMediaOnly.findIndex(
|
||||
(img: any) => img.id === item.id,
|
||||
(img: MediaItem) => img.id === item.id,
|
||||
);
|
||||
setIndex(photoIndex);
|
||||
} else {
|
||||
@@ -93,7 +96,9 @@ export default function ApplicationDetailModal({
|
||||
};
|
||||
|
||||
const handleDeleteApplication = async () => {
|
||||
if (!application) return;
|
||||
await apiDeleteHistorianCV(application.id);
|
||||
|
||||
Swal.fire("Thành công!", "Hồ sơ đã được xóa.", "success");
|
||||
onRefresh();
|
||||
onClose();
|
||||
@@ -186,7 +191,7 @@ export default function ApplicationDetailModal({
|
||||
</h4>
|
||||
{mediaList.length > 0 ? (
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4">
|
||||
{mediaList.map((media: any, idx: number) => {
|
||||
{mediaList.map((media: MediaItem, idx: number) => {
|
||||
const isImg = isImageFile(media);
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -23,7 +23,8 @@ export interface MediaItem {
|
||||
original_name: string;
|
||||
mime_type: string;
|
||||
size: number;
|
||||
file_metadata: any;
|
||||
file_metadata: Record<string, unknown> | null;
|
||||
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { Modal } from "../ui/modal";
|
||||
import UserMetaCard from "@/components/user-profile/UserMetaCard";
|
||||
import UserInfoCard from "@/components/user-profile/UserInfoCard";
|
||||
import { UserMetaCardProps } from "@/interface/user";
|
||||
|
||||
import { fullDataUser } from "@/interface/admin";
|
||||
import { useEffect, useState } from "react";
|
||||
import { MediaDto } from "@/interface/media";
|
||||
@@ -28,7 +30,17 @@ export default function UserDetailModal({
|
||||
const [mediaData, setMediaData] = useState<MediaDto | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const formattedData = { data: user };
|
||||
const formattedData: UserMetaCardProps = {
|
||||
data: user
|
||||
? {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
profile: user.profile,
|
||||
roles: user.roles as any, // UserRole and Role are slightly different, need a better fix or small cast
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.id && isOpen) {
|
||||
@@ -68,8 +80,9 @@ export default function UserDetailModal({
|
||||
</div>
|
||||
|
||||
<div className="space-y-6 custom-scrollbar max-h-[65vh] overflow-y-auto pr-2">
|
||||
<UserMetaCard data={formattedData as any} />
|
||||
<UserInfoCard data={formattedData as any} />
|
||||
<UserMetaCard data={formattedData} />
|
||||
<UserInfoCard data={formattedData} />
|
||||
|
||||
|
||||
<div className="min-h-[150px] relative">
|
||||
{loading ? (
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { ChatbotPayload } from "@/interface/chatbot";
|
||||
import { apiChatbot } from "@/service/chatbotService";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
|
||||
type Message = {
|
||||
id: string;
|
||||
@@ -67,16 +69,18 @@ export default function ChatbotWidget({
|
||||
};
|
||||
|
||||
setMessages((prev) => [...prev, botMessage]);
|
||||
} catch (error: any) {
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError<{ message: string }>;
|
||||
const errorMessage: Message = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
sender: "bot",
|
||||
text:
|
||||
error?.response?.data?.message ||
|
||||
axiosError.response?.data?.message ||
|
||||
"Có lỗi xảy ra khi kết nối. Vui lòng thử lại sau.",
|
||||
};
|
||||
setMessages((prev) => [...prev, errorMessage]);
|
||||
} finally {
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,7 +20,10 @@ const formatFullDateTime = (dateString: string) => {
|
||||
return `${time} ${day}`;
|
||||
};
|
||||
|
||||
const processMedia = (mediaArray: any[]) => {
|
||||
import { Application } from "@/interface/historian";
|
||||
import { MediaItem } from "../tables/MediaTable";
|
||||
|
||||
const processMedia = (mediaArray: MediaItem[]) => {
|
||||
if (!mediaArray || mediaArray.length === 0) return { type: "empty" };
|
||||
const imageFiles = mediaArray.filter((file) => {
|
||||
const isImageMime = file.mime_type?.startsWith("image/");
|
||||
@@ -49,15 +52,16 @@ const processMedia = (mediaArray: any[]) => {
|
||||
export default function ApplicationList({
|
||||
applications,
|
||||
}: {
|
||||
applications: any[];
|
||||
applications: Application[];
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleViewDetail = (app: any) => {
|
||||
const handleViewDetail = (app: Application) => {
|
||||
dispatch(setSelectedApplication(app));
|
||||
router.push(`/user/account/applications`);
|
||||
};
|
||||
|
||||
|
||||
const StatusIcons: Record<string, React.ReactNode> = {
|
||||
APPROVED: (
|
||||
|
||||
@@ -11,6 +11,8 @@ import { URL_MEDIA } from "../../../api";
|
||||
|
||||
import { deleteMedia } from "@/service/mediaService";
|
||||
import { INITIAL_LIMIT } from "../../../constant";
|
||||
import { MediaItem } from "../tables/MediaTable";
|
||||
|
||||
|
||||
export default function MediaLibrary({
|
||||
data,
|
||||
@@ -32,7 +34,7 @@ export default function MediaLibrary({
|
||||
setLocalMedia(data?.data || []);
|
||||
}, [data]);
|
||||
|
||||
const isImageFile = (file: any) => {
|
||||
const isImageFile = (file: MediaItem) => {
|
||||
const isImageMime = file.mime_type?.startsWith("image/");
|
||||
const isImageExt = /\.(jpg|jpeg|png|webp|gif)$/i.test(file.storage_key);
|
||||
return isImageMime || isImageExt;
|
||||
@@ -77,7 +79,7 @@ export default function MediaLibrary({
|
||||
}
|
||||
};
|
||||
|
||||
const handleItemClick = (item: any, idx: number, isImage: boolean) => {
|
||||
const handleItemClick = (item: MediaItem, idx: number, isImage: boolean) => {
|
||||
if (isSelectionMode) {
|
||||
toggleItemSelection(item.id);
|
||||
} else {
|
||||
@@ -166,7 +168,7 @@ export default function MediaLibrary({
|
||||
}
|
||||
};
|
||||
|
||||
const renderItemCard = (item: any, isImage: boolean, idx: number) => {
|
||||
const renderItemCard = (item: MediaItem, isImage: boolean, idx: number) => {
|
||||
const isSelected = selectedIds.includes(item.id);
|
||||
|
||||
return (
|
||||
@@ -254,6 +256,7 @@ export default function MediaLibrary({
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border border-gray-200 bg-white p-6 dark:border-zinc-800 dark:bg-zinc-900/50">
|
||||
<div className="mb-6 flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
|
||||
|
||||
@@ -10,6 +10,8 @@ import { Profile, UserMetaCardProps } from "@/interface/user";
|
||||
import { apiUpdateUser } from "@/service/userService";
|
||||
import { toast } from "sonner";
|
||||
import Link from "next/link";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
|
||||
export default function UserInfoCard({ data }: { data: UserMetaCardProps }) {
|
||||
const router = useRouter();
|
||||
@@ -62,8 +64,9 @@ export default function UserInfoCard({ data }: { data: UserMetaCardProps }) {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} catch (error: any) {
|
||||
const serverResponse = error.response?.data;
|
||||
} catch (error) {
|
||||
const axiosError = error as AxiosError<{ status: boolean; message: string }>;
|
||||
const serverResponse = axiosError.response?.data;
|
||||
|
||||
if (serverResponse && serverResponse.status === false) {
|
||||
const msg = serverResponse.message || "";
|
||||
@@ -77,8 +80,9 @@ export default function UserInfoCard({ data }: { data: UserMetaCardProps }) {
|
||||
toast.error("Không thể kết nối đến máy chủ hoặc lỗi hệ thống.");
|
||||
}
|
||||
|
||||
console.error("Lỗi chi tiết:", error);
|
||||
console.error("Lỗi chi tiết:", axiosError);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user