delete note

This commit is contained in:
Admin 2025-05-07 08:32:15 +07:00
parent 77019ec8b3
commit 8b6565a163
5 changed files with 204 additions and 134 deletions

View File

@ -4,7 +4,7 @@ import { app, BrowserWindow, ipcMain, Menu, Notification, screen } from 'electro
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { io } from 'socket.io-client'; import { io } from 'socket.io-client';
import { addEmail, deleteEmail, fetchEmails, fetchMessages } from '../src/apis'; import { addEmail, deleteEmail, deleteMessage, fetchEmails, fetchMessages } from '../src/apis';
import { createMailWindow } from './windows/mails.window'; import { createMailWindow } from './windows/mails.window';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
@ -53,8 +53,8 @@ function createWindow() {
// Vị trí cửa sổ ở góc phải dưới của màn hình đã chọn // Vị trí cửa sổ ở góc phải dưới của màn hình đã chọn
win = new BrowserWindow({ win = new BrowserWindow({
width: 600, width: 400,
height: 200, height: 400,
x: 0, // Đặt cửa sổ ở góc phải x: 0, // Đặt cửa sổ ở góc phải
y: 0, // Đặt cửa sổ ở góc dưới y: 0, // Đặt cửa sổ ở góc dưới
alwaysOnTop: true, // Cửa sổ luôn nằm trên các cửa sổ khác alwaysOnTop: true, // Cửa sổ luôn nằm trên các cửa sổ khác
@ -70,7 +70,7 @@ function createWindow() {
win?.webContents.send('main-process-message', new Date().toLocaleString()); win?.webContents.send('main-process-message', new Date().toLocaleString());
}); });
win.setPosition(width - 600, height - 200); win.setPosition(width - 400, height - 400);
if (VITE_DEV_SERVER_URL) { if (VITE_DEV_SERVER_URL) {
win.loadURL(VITE_DEV_SERVER_URL); win.loadURL(VITE_DEV_SERVER_URL);
@ -148,6 +148,10 @@ ipcMain.handle('del-email', async (_, id: number) => {
return deleteEmail(id); return deleteEmail(id);
}); });
ipcMain.handle('del-message', async (_, id: number) => {
return deleteMessage(id);
});
ipcMain.handle('show-notification', async (_, { title, body }) => { ipcMain.handle('show-notification', async (_, { title, body }) => {
const notification = new Notification({ const notification = new Notification({
title, title,

View File

@ -1,55 +1,62 @@
import axios from '../instants/axios'; import axios from "../instants/axios";
const API_KEY = import.meta.env.VITE_API_KEY; const API_KEY = import.meta.env.VITE_API_KEY;
export const getAllNote = async () => { export const getAllNote = async () => {
try { try {
const response = await axios({ const response = await axios({
method: 'GET', method: "GET",
url: 'getAllNotes', url: "getAllNotes",
}); });
return response.data; return response.data;
} catch (error) { } catch (error) {
return []; return [];
} }
}; };
export async function fetchMessages(onError?: (error: unknown) => void) { export async function fetchMessages(onError?: (error: unknown) => void) {
try { try {
const response = await axios.get(`getAllNotes?key=${API_KEY}`); const response = await axios.get(`getAllNotes?key=${API_KEY}`);
return response.data; return response.data;
} catch (error) { } catch (error) {
console.error('Error fetching notes:', error); console.error("Error fetching notes:", error);
onError?.(error); onError?.(error);
} }
} }
export async function fetchEmails(onError?: (error: unknown) => void) { export async function fetchEmails(onError?: (error: unknown) => void) {
try { try {
const response = await axios.get(`emails?key=${API_KEY}`); const response = await axios.get(`emails?key=${API_KEY}`);
return response.data; return response.data;
} catch (error) { } catch (error) {
console.error('Error fetching emails:', error); console.error("Error fetching emails:", error);
onError?.(error); onError?.(error);
} }
} }
export async function addEmail(email: string, onError?: (error: unknown) => void) { export async function addEmail(
try { email: string,
const response = await axios.post(`add-email`, { onError?: (error: unknown) => void
email: email, ) {
key: API_KEY, try {
}); const response = await axios.post(`add-email`, {
email: email,
key: API_KEY,
});
return response.data; return response.data;
} catch (error) { } catch (error) {
console.error('Error add email:', error); console.error("Error add email:", error);
onError?.(error); onError?.(error);
} }
} }
export async function deleteEmail(id: number) { export async function deleteEmail(id: number) {
await axios.delete(`delete-email/${id}?key=${API_KEY}`); await axios.delete(`delete-email/${id}?key=${API_KEY}`);
}
export async function deleteMessage(id: number) {
await axios.delete(`delete-note/${id}?key=${API_KEY}`);
} }

View File

@ -5,9 +5,10 @@ import { useEffect, useMemo, useState } from 'react';
export interface IMessageProps { export interface IMessageProps {
data: IMessage; data: IMessage;
onDelete: () => void
} }
export default function Message({ data }: IMessageProps) { export default function Message({ data, onDelete }: IMessageProps) {
const [show, { toggle }] = useDisclosure(false); const [show, { toggle }] = useDisclosure(false);
const [animation, setAnimation] = useState(false); const [animation, setAnimation] = useState(false);
@ -27,7 +28,7 @@ export default function Message({ data }: IMessageProps) {
}, [animation]); }, [animation]);
useEffect(() => { useEffect(() => {
const showAnimation = new Date().getTime() - new Date(data.time).getTime() <= 3000; const showAnimation = new Date().getTime() - new Date(data.time).getTime() <= 110000;
setAnimation(showAnimation); setAnimation(showAnimation);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -51,7 +52,7 @@ export default function Message({ data }: IMessageProps) {
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
<Tooltip label="Delete"> <Tooltip label="Delete">
<ActionIcon variant="light" size="sm" color="red"> <ActionIcon onClick={onDelete} variant="light" size="sm" color="red">
<IconTrash size={16} /> <IconTrash size={16} />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>

View File

@ -1,11 +1,14 @@
import { Menu } from "@mantine/core"; import { Menu } from "@mantine/core";
import { IconCode } from "@tabler/icons-react"; import { IconCode, IconRefresh } from "@tabler/icons-react";
import { ReactNode } from "react"; import { ReactNode } from "react";
export interface ISettingsProps { export interface ISettingsProps {
children: ReactNode; children: ReactNode;
funtions?: {
refresh?: () => void
}
} }
export default function Settings({ children }: ISettingsProps) { export default function Settings({ children, funtions }: ISettingsProps) {
const openDevtools = () => { const openDevtools = () => {
window.ipcRenderer?.openDevTools(); window.ipcRenderer?.openDevTools();
}; };
@ -19,6 +22,9 @@ export default function Settings({ children }: ISettingsProps) {
<Menu.Item onClick={openDevtools} leftSection={<IconCode size={12} />} className="text-xs"> <Menu.Item onClick={openDevtools} leftSection={<IconCode size={12} />} className="text-xs">
Devtool Devtool
</Menu.Item> </Menu.Item>
<Menu.Item onClick={funtions?.refresh} leftSection={<IconRefresh size={12} />} className="text-xs">
Refresh
</Menu.Item>
</Menu.Dropdown> </Menu.Dropdown>
</Menu> </Menu>
); );

View File

@ -1,106 +1,158 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { ActionIcon, Box, LoadingOverlay, ScrollAreaAutosize, Tooltip } from '@mantine/core'; import {
import { useViewportSize } from '@mantine/hooks'; // 🔥 thêm cái này ActionIcon,
import { IconDotsVertical, IconMail } from '@tabler/icons-react'; Box,
import { useEffect, useRef, useState } from 'react'; LoadingOverlay,
import { Message, Settings } from '../components'; ScrollAreaAutosize,
import { showNotification } from '../ultils/fn'; Tooltip,
} from "@mantine/core";
import { useDisclosure, useViewportSize } from "@mantine/hooks"; // 🔥 thêm cái này
import { IconDotsVertical, IconMail } from "@tabler/icons-react";
import { useEffect, useRef, useState } from "react";
import { ConfirmModal, Message, Settings } from "../components";
import { showNotification } from "../ultils/fn";
function MainPage() { function MainPage() {
const [messages, setMessages] = useState<IMessage[]>([]); const [messages, setMessages] = useState<IMessage[]>([]);
const viewportRef = useRef<HTMLDivElement>(null); const viewportRef = useRef<HTMLDivElement>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [opened, { open, close }] = useDisclosure(false);
const { height } = useViewportSize(); // lấy kích thước cửa sổ
const [clickMessage, setClickMessage] = useState<IMessage | null>(null);
const scrollToBottom = () => {
const viewport = viewportRef.current;
if (viewport) {
viewport.scrollTo({
top: viewport.scrollHeight,
behavior: "smooth",
});
}
};
const { height } = useViewportSize(); // lấy kích thước cửa sổ const fetchMessages = async () => {
const messages = await window.ipcRenderer.invoke("fetchMessages");
const scrollToBottom = () => { if (!messages || !Array.isArray(messages)) {
const viewport = viewportRef.current; showNotification("Lỗi fetch notes", `Error: ${JSON.stringify(messages)}`);
if (viewport) { return;
viewport.scrollTo({ }
top: viewport.scrollHeight,
behavior: 'smooth', setMessages(messages);
}); };
}
const handleDeleteMessage = async (id?: number) => {
try {
if (!id) return;
setLoading(true);
await window.ipcRenderer.invoke("del-message", id);
await fetchMessages();
close();
showNotification("Thông báo", "Đã xóa note: " + clickMessage?.id);
setClickMessage(null)
} catch (error) {
console.log("%csrc/pages/mails.tsx:88 error", "color: #007acc;", error);
} finally {
setLoading(false);
}
};
useEffect(() => {
const connectSocket = async () => {
try {
await window.ipcRenderer.invoke("connect-socket");
setLoading(true);
await fetchMessages();
setTimeout(scrollToBottom, 100);
} catch (error) {
console.error("Failed to connect socket", error);
} finally {
setLoading(false);
}
}; };
const fetchMessages = async () => { connectSocket();
const messages = await window.ipcRenderer.invoke('fetchMessages'); window.ipcRenderer?.onNewNote(async (data: any) => {
console.log("Received newNote data:", data);
showNotification("Có tin nhắn mới", data.message);
await fetchMessages();
setTimeout(() => scrollToBottom(), 100);
});
}, []);
if (!messages || !Array.isArray(messages)) { const openMailsWindow = () => {
showNotification('Lỗi fetch notes', `Error: ${JSON.stringify(messages)}`); window.ipcRenderer.invoke("open-new-window");
return; };
}
setMessages(messages); // 🧠 Tính chiều cao dynamic (ví dụ trừ header 80px + padding 30px)
}; const scrollAreaHeight = height - 70; // Bạn chỉnh số này nếu muốn
useEffect(() => { return (
const connectSocket = async () => { <Box className="flex flex-col !overflow-hidden h-full">
try { {/* Header */}
await window.ipcRenderer.invoke('connect-socket'); <header className="p-4 flex items-center justify-between sticky top-0 border-b border-gray-100 pb-2 bg-white z-10">
setLoading(true); <Tooltip label="Mail">
await fetchMessages(); <ActionIcon
setTimeout(scrollToBottom, 100); onClick={openMailsWindow}
} catch (error) { variant="light"
console.error('Failed to connect socket', error); radius="md"
} finally { size="lg"
setLoading(false); >
} <IconMail size={18} />
}; </ActionIcon>
</Tooltip>
connectSocket(); <div className="text-xl font-semibold">Notes</div>
window.ipcRenderer?.onNewNote(async (data: any) => {
console.log('Received newNote data:', data);
showNotification('Có tin nhắn mới', data.message);
await fetchMessages();
scrollToBottom();
});
}, []);
const openMailsWindow = () => { <Settings
window.ipcRenderer.invoke('open-new-window'); funtions={{
}; refresh: async () => {
setLoading(true);
await fetchMessages();
setLoading(false);
},
}}
>
<Tooltip label="Settings">
<ActionIcon variant="subtle" radius="md" size="lg">
<IconDotsVertical size={18} />
</ActionIcon>
</Tooltip>
</Settings>
</header>
// 🧠 Tính chiều cao dynamic (ví dụ trừ header 80px + padding 30px) {/* Message List */}
const scrollAreaHeight = height - 70; // Bạn chỉnh số này nếu muốn <ScrollAreaAutosize
viewportRef={viewportRef}
return ( h={scrollAreaHeight} // 👈 set height động theo window size
<Box className="flex flex-col !overflow-hidden h-full"> type="auto"
{/* Header */} >
<header className="p-4 flex items-center justify-between sticky top-0 border-b border-gray-100 pb-2 bg-white z-10"> <Box className="flex flex-col gap-3 w-full overflow-hidden p-2">
<Tooltip label="Mail"> {messages.map((item) => (
<ActionIcon onClick={openMailsWindow} variant="light" radius="md" size="lg"> <Message
<IconMail size={18} /> onDelete={() => {
</ActionIcon> setClickMessage(item);
</Tooltip> open();
}}
<div className="text-xl font-semibold">Notes</div> key={item.message + item.id}
data={item}
<Settings> />
<Tooltip label="Settings"> ))}
<ActionIcon variant="subtle" radius="md" size="lg">
<IconDotsVertical size={18} />
</ActionIcon>
</Tooltip>
</Settings>
</header>
{/* Message List */}
<ScrollAreaAutosize
viewportRef={viewportRef}
h={scrollAreaHeight} // 👈 set height động theo window size
type="auto"
>
<Box className="flex flex-col gap-3 w-full overflow-hidden p-2">
{messages.map((item) => (
<Message key={item.message + item.id} data={item} />
))}
</Box>
</ScrollAreaAutosize>
<LoadingOverlay visible={loading} />
</Box> </Box>
); </ScrollAreaAutosize>
<LoadingOverlay visible={loading} />
<ConfirmModal
title="Warning"
message={`Bạn có chắc chắn muốn xóa note "${clickMessage?.id}" không?`}
opened={opened}
onCancel={close}
onConfirm={() => handleDeleteMessage(clickMessage?.id)}
/>
</Box>
);
} }
export default MainPage; export default MainPage;