feat:notif
This commit is contained in:
parent
f0cbdab261
commit
018f23f833
|
|
@ -2908,3 +2908,45 @@ export const AddAgentIcon = ({
|
|||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const NotificationIcon = ({
|
||||
size,
|
||||
height = 24,
|
||||
width = 24,
|
||||
fill = "currentColor",
|
||||
...props
|
||||
}: IconSvgProps) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size || width}
|
||||
height={size || height}
|
||||
{...props}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M5 19q-.425 0-.712-.288T4 18t.288-.712T5 17h1v-7q0-2.075 1.25-3.687T10.5 4.2v-.7q0-.625.438-1.062T12 2t1.063.438T13.5 3.5v.7q2 .5 3.25 2.113T18 10v7h1q.425 0 .713.288T20 18t-.288.713T19 19zm7 3q-.825 0-1.412-.587T10 20h4q0 .825-.587 1.413T12 22"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const NotificationUnreadIcon = ({
|
||||
size,
|
||||
height = 24,
|
||||
width = 24,
|
||||
fill = "currentColor",
|
||||
...props
|
||||
}: IconSvgProps) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size || width}
|
||||
height={size || height}
|
||||
{...props}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12 22q-.825 0-1.412-.587T10 20h4q0 .825-.587 1.413T12 22m-7-3q-.425 0-.712-.288T4 18t.288-.712T5 17h1v-7q0-2.075 1.25-3.687T10.5 4.2v-.7q0-.625.438-1.062T12 2t1.063.438T13.5 3.5v.325q-.25.5-.375 1.05T13 6q0 2.075 1.463 3.538T18 11v6h1q.425 0 .713.288T20 18t-.288.713T19 19zM18 9q-1.25 0-2.125-.875T15 6t.875-2.125T18 3t2.125.875T21 6t-.875 2.125T18 9"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,25 @@ import {
|
|||
} from "./ui/dropdown-menu";
|
||||
import Cookies from "js-cookie";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
getNotificationsData,
|
||||
mareReadNotification,
|
||||
} from "@/service/notification";
|
||||
import {
|
||||
getCookiesDecrypt,
|
||||
getTimeStamp,
|
||||
parseDateNoTZ,
|
||||
} from "@/utils/globals";
|
||||
import { NotificationIcon, NotificationUnreadIcon } from "./icons";
|
||||
|
||||
interface NotifList {
|
||||
id: number;
|
||||
isRead: boolean;
|
||||
sendBy: number;
|
||||
sendByName: string;
|
||||
message: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export default function Navbar(props: {
|
||||
title: string;
|
||||
|
|
@ -21,17 +40,22 @@ export default function Navbar(props: {
|
|||
subSubTitle?: string;
|
||||
}) {
|
||||
const [isCollapsed, setIsCollapsed] = useState(
|
||||
typeof window !== "undefined" && localStorage.getItem("sidebar") === "open"
|
||||
typeof window !== "undefined" && localStorage.getItem("sidebar") === "open",
|
||||
);
|
||||
const { title, subTitle, subSubTitle } = props;
|
||||
const titleCondition = subSubTitle
|
||||
? "subSubTitle"
|
||||
: subTitle
|
||||
? "subTitle"
|
||||
: "title";
|
||||
? "subTitle"
|
||||
: "title";
|
||||
|
||||
const fullname = Cookies.get("ufne");
|
||||
const username = Cookies.get("username");
|
||||
const userId = String(getCookiesDecrypt("uie"));
|
||||
|
||||
const [notifList, setNotifList] = useState<NotifList[]>([]);
|
||||
const [isUnread, setIsUnread] = useState(true);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -41,12 +65,10 @@ export default function Navbar(props: {
|
|||
}, [fullname]);
|
||||
|
||||
const handleLogout = () => {
|
||||
// Hapus semua cookies
|
||||
Object.keys(Cookies.get()).forEach((cookieName) => {
|
||||
Cookies.remove(cookieName);
|
||||
});
|
||||
|
||||
// Redirect ke halaman login
|
||||
router.push("/auth");
|
||||
};
|
||||
|
||||
|
|
@ -65,15 +87,33 @@ export default function Navbar(props: {
|
|||
setIsCollapsed(newState);
|
||||
localStorage.setItem("sidebar", newState ? "open" : "close");
|
||||
|
||||
// ✅ Kirim event manual agar sidebar langsung tahu (tanpa tunggu reload)
|
||||
window.dispatchEvent(
|
||||
new StorageEvent("storage", {
|
||||
key: "sidebar",
|
||||
newValue: newState ? "open" : "close",
|
||||
})
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getNotifications();
|
||||
}, []);
|
||||
|
||||
const getNotifications = async () => {
|
||||
const res = await getNotificationsData(userId, 1, 5);
|
||||
const data: NotifList[] = res?.data?.data ?? [];
|
||||
const temp = data.map((a) => {
|
||||
return a.isRead;
|
||||
});
|
||||
setIsUnread(temp.includes(false));
|
||||
setNotifList(data);
|
||||
};
|
||||
|
||||
const markRead = async (id: number) => {
|
||||
const res = await mareReadNotification(id);
|
||||
getNotifications();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full flex justify-between items-center h-8 lg:text-2xl mt-2 lg:mt-0">
|
||||
<div className="flex flex-row">
|
||||
|
|
@ -137,6 +177,29 @@ export default function Navbar(props: {
|
|||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-gray-500" />
|
||||
<Input placeholder="Search" className="pl-8 text-sm h-8 w-48" />
|
||||
</div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<a className="cursor-pointer">
|
||||
{isUnread ? <NotificationUnreadIcon /> : <NotificationIcon />}
|
||||
</a>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56" align="start">
|
||||
<div className="flex flex-col gap-1 text-sm">
|
||||
{notifList.map((notif) => (
|
||||
<a
|
||||
key={notif.id}
|
||||
className={`cursor-pointer flex flex-col ${notif.isRead ? "" : "bg-gray-100"}`}
|
||||
onClick={() => markRead(notif.id)}
|
||||
>
|
||||
<p>From: {notif.sendByName}</p>
|
||||
<p className="text-xs">{notif.message}</p>
|
||||
|
||||
<p>{getTimeStamp(parseDateNoTZ(notif.createdAt))}</p>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import { getCookiesDecrypt } from "@/utils/globals";
|
|||
import Swal from "sweetalert2";
|
||||
import withReactContent from "sweetalert2-react-content";
|
||||
import { close, error, loading } from "@/config/swal";
|
||||
import { sendNotifications } from "@/service/notification";
|
||||
import Cookies from "js-cookie";
|
||||
|
||||
interface KnowledgeBase {
|
||||
id: number;
|
||||
|
|
@ -42,6 +44,7 @@ export default function DataKnowledge() {
|
|||
const [totalData, setTotalData] = useState(0);
|
||||
const ulne = getCookiesDecrypt("ulne");
|
||||
const uie = getCookiesDecrypt("uie");
|
||||
const ufne = Cookies.get("ufne");
|
||||
|
||||
useEffect(() => {
|
||||
initFetch();
|
||||
|
|
@ -82,6 +85,18 @@ export default function DataKnowledge() {
|
|||
if (statusNumber == 1) {
|
||||
await sendFileKnowledgeBase(id);
|
||||
}
|
||||
|
||||
if (statusNumber == 2) {
|
||||
const findKB = knowledgeBase.find((a) => a.id == id);
|
||||
|
||||
const req = {
|
||||
sentTo: Number(findKB?.createdById),
|
||||
sendBy: Number(uie),
|
||||
sendByName: ufne as string,
|
||||
message: `Data Knowlegde ${findKB?.title} Rejected`,
|
||||
};
|
||||
const resNotif = await sendNotifications(req);
|
||||
}
|
||||
initFetch();
|
||||
MySwal.fire({
|
||||
title: "Sukses",
|
||||
|
|
@ -128,7 +143,6 @@ export default function DataKnowledge() {
|
|||
tempFileName.push(getDocumentName(fileVideoUrl));
|
||||
}
|
||||
|
||||
console.log("tempfilename", tempFileName);
|
||||
const res = await deleteKnowledgeBaseData(id);
|
||||
if (res?.error) {
|
||||
error(res?.message);
|
||||
|
|
@ -141,7 +155,7 @@ export default function DataKnowledge() {
|
|||
`https://narasiahli.com/ai/api/v1/agents/${data?.agentId}/s3-documents/${element}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -176,7 +190,7 @@ export default function DataKnowledge() {
|
|||
{
|
||||
method: "POST",
|
||||
body: formData,
|
||||
}
|
||||
},
|
||||
);
|
||||
// const json = await res.json();
|
||||
|
||||
|
|
@ -281,16 +295,16 @@ export default function DataKnowledge() {
|
|||
item.status == 0
|
||||
? "bg-orange-300"
|
||||
: item.status == 1
|
||||
? "bg-green-600"
|
||||
: "bg-red-600"
|
||||
? "bg-green-600"
|
||||
: "bg-red-600"
|
||||
}`}
|
||||
>
|
||||
{" "}
|
||||
{item.status == 0
|
||||
? "Waiting"
|
||||
: item.status == 1
|
||||
? "Approved"
|
||||
: "Rejected"}
|
||||
? "Approved"
|
||||
: "Rejected"}
|
||||
</p>
|
||||
</td>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
import {
|
||||
httpGetInterceptor,
|
||||
httpPostInterceptor,
|
||||
httpPutInterceptor,
|
||||
httpPatchInterceptor,
|
||||
httpDeleteInterceptor,
|
||||
} from "./http-config/http-interceptor-services";
|
||||
|
||||
import { httpGet } from "./http-config/http-base-services";
|
||||
|
||||
export async function getNotificationsData(
|
||||
user: string,
|
||||
page: number,
|
||||
limit: number,
|
||||
status?: boolean,
|
||||
) {
|
||||
const response = await httpGet(
|
||||
`/notifications/${user}?page=${page}&limit=${limit}${status ? `&status${status}` : ""}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function mareReadNotification(id: number) {
|
||||
const response = await httpPutInterceptor(`/notifications/${id}/read`, {});
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function sendNotifications(data: {
|
||||
message: string;
|
||||
sendBy: number;
|
||||
sendByName: string;
|
||||
sentTo: number;
|
||||
}) {
|
||||
const response = await httpPostInterceptor(`/notifications`, data);
|
||||
return response;
|
||||
}
|
||||
|
|
@ -77,7 +77,7 @@ export function delay(ms: number) {
|
|||
export function textEllipsis(
|
||||
str: string,
|
||||
maxLength: number,
|
||||
{ side = "end", ellipsis = "..." } = {}
|
||||
{ side = "end", ellipsis = "..." } = {},
|
||||
) {
|
||||
if (str !== undefined && str?.length > maxLength) {
|
||||
switch (side) {
|
||||
|
|
@ -141,12 +141,12 @@ export function formatMonthString(dateString: string) {
|
|||
export function setCookiesEncrypt(
|
||||
param: string,
|
||||
data: any,
|
||||
options?: Cookies.CookieAttributes
|
||||
options?: Cookies.CookieAttributes,
|
||||
) {
|
||||
// Enkripsi data
|
||||
const cookiesEncrypt = CryptoJS.AES.encrypt(
|
||||
JSON.stringify(data),
|
||||
`${param}_EncryptKey@humas`
|
||||
`${param}_EncryptKey@humas`,
|
||||
).toString(); // Tambahkan .toString() di sini
|
||||
|
||||
// Simpan data terenkripsi di cookie
|
||||
|
|
@ -159,7 +159,7 @@ export function getCookiesDecrypt(param: any) {
|
|||
if (cookiesEncrypt != undefined) {
|
||||
const output = CryptoJS.AES.decrypt(
|
||||
cookiesEncrypt.toString(),
|
||||
`${param}_EncryptKey@humas`
|
||||
`${param}_EncryptKey@humas`,
|
||||
).toString(CryptoJS.enc.Utf8);
|
||||
if (output.startsWith('"')) {
|
||||
return output.slice(1, -1);
|
||||
|
|
@ -185,3 +185,47 @@ export function createSlug(text?: string) {
|
|||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export const getTimeStamp = (createdAt: Date): string => {
|
||||
const now = new Date();
|
||||
const differenceInSeconds = Math.floor(
|
||||
(now.getTime() - createdAt.getTime()) / 1000,
|
||||
);
|
||||
|
||||
console.log("adatw", differenceInSeconds, now, createdAt);
|
||||
|
||||
const intervals: { [key: string]: number } = {
|
||||
year: 31536000,
|
||||
month: 2592000,
|
||||
week: 604800,
|
||||
day: 86400,
|
||||
hour: 3600,
|
||||
minute: 60,
|
||||
second: 1,
|
||||
};
|
||||
|
||||
for (const interval in intervals) {
|
||||
const intervalSeconds = intervals[interval];
|
||||
const count = Math.floor(differenceInSeconds / intervalSeconds);
|
||||
|
||||
if (count >= 1) {
|
||||
return `${count} ${interval}${count > 1 ? "s" : ""} ago`;
|
||||
}
|
||||
}
|
||||
|
||||
return "just now";
|
||||
};
|
||||
|
||||
export const parseDateNoTZ = (iso: string): Date => {
|
||||
const [datePart, timePartRaw] = iso.split("T");
|
||||
|
||||
const [year, month, day] = datePart.split("-").map(Number);
|
||||
|
||||
const timePart = timePartRaw ?? "00:00:00";
|
||||
const [hh, mm, ssRaw] = timePart.split(":");
|
||||
|
||||
const seconds = Number(ssRaw?.split(".")[0] ?? 0);
|
||||
const ms = Number((ssRaw?.split(".")[1] ?? "0").slice(0, 3));
|
||||
|
||||
return new Date(year, month - 1, day, Number(hh), Number(mm), seconds, ms);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue