-
Notifications
You must be signed in to change notification settings - Fork 0
Refactor admin page #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,220 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useEffect, useMemo, useState, useCallback, useRef } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { BlockNoteView } from "@blocknote/mantine"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useCreateBlockNote } from "@blocknote/react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import "@blocknote/core/fonts/inter.css"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import "@blocknote/mantine/style.css"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Button } from "antd"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { FullscreenOutlined, FullscreenExitOutlined } from "@ant-design/icons"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import styled from "styled-components"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import PropTypes from "prop-types"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const BlockNoteEditor = ({ initialContent, onChange, placeholder = "Nhập nội dung bài viết..." }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [isFullscreen, setIsFullscreen] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const onChangeRef = useRef(onChange); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Update ref when onChange changes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChangeRef.current = onChange; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [onChange]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Parse initial content | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const parsedInitialContent = useMemo(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!initialContent) return undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (typeof initialContent === 'string' && initialContent.trim().startsWith('<')) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return undefined; // BlockNote will use default blocks | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (Array.isArray(initialContent)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return initialContent; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error('Error parsing initial content:', error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [initialContent]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create editor instance | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const editor = useCreateBlockNote({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| initialContent: parsedInitialContent, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange: () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| handleEditorChange(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+41
to
+46
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const editor = useCreateBlockNote({ | |
| initialContent: parsedInitialContent, | |
| onChange: () => { | |
| handleEditorChange(); | |
| }, | |
| }); | |
| // Use a ref to hold the latest editor instance | |
| const editorRef = useRef(null); | |
| const editor = useCreateBlockNote({ | |
| initialContent: parsedInitialContent, | |
| onChange: () => { | |
| // Use the ref to access the latest editor instance if needed | |
| if (onChangeRef.current) { | |
| // If you need to pass the editor's content, use editorRef.current | |
| // For example: onChangeRef.current(editorRef.current?.getContent()); | |
| // If not, just call onChangeRef.current() | |
| onChangeRef.current(); | |
| } | |
| }, | |
| }); | |
| // Update the ref whenever editor changes | |
| useEffect(() => { | |
| editorRef.current = editor; | |
| }, [editor]); |
Copilot
AI
Dec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate change handler subscription. The editor's onChange is already set in useCreateBlockNote (line 43), but then another subscription is created in the useEffect (line 102). This will cause the change handler to fire twice for every edit, which is inefficient and could lead to performance issues or unexpected behavior.
| useEffect(() => { | |
| if (!editor) return; | |
| let timeoutId = null; | |
| // Debounce the change handler to avoid rapid successive calls | |
| const debouncedHandler = () => { | |
| if (timeoutId) { | |
| clearTimeout(timeoutId); | |
| } | |
| timeoutId = setTimeout(() => { | |
| handleEditorChange(); | |
| }, 100); | |
| }; | |
| // Subscribe to editor changes | |
| const unsubscribe = editor.onChange(debouncedHandler); | |
| return () => { | |
| if (timeoutId) { | |
| clearTimeout(timeoutId); | |
| } | |
| if (typeof unsubscribe === 'function') { | |
| unsubscribe(); | |
| } | |
| }; | |
| }, [editor, handleEditorChange]); | |
| // Removed duplicate editor.onChange subscription useEffect. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ const generateBasicColumn = ( | |
| // title: () => <ColumnSort type="id" title="ID" handleSort={handleSort} />, | ||
| dataIndex: "id", | ||
| key: "id", | ||
| width: 200, | ||
|
||
| sorter: true, | ||
| sortOrder: filter.sort.sortBy === "id" ? filter.sort.sortType : false, | ||
| onHeaderCell: (column) => ({ | ||
|
|
@@ -102,6 +103,7 @@ const generateBasicColumn = ( | |
| dataIndex: "action", | ||
| key: "action", | ||
| fixed: "right", | ||
| width: 200, | ||
| render: (_, record) => ( | ||
| <Space size="middle"> | ||
| <ButtonShow id={record.id} /> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import { Card } from 'antd'; | ||
| import PropTypes from 'prop-types'; | ||
|
|
||
| /** | ||
| * ChartCard - Card wrapper cho charts với styling đẹp | ||
| */ | ||
| const ChartCard = ({ | ||
| title, | ||
| children, | ||
| extra = null, | ||
| height = 400, | ||
| loading = false, | ||
| className = '' | ||
| }) => { | ||
| return ( | ||
| <Card | ||
| title={title} | ||
| extra={extra} | ||
| loading={loading} | ||
| className={`shadow-sm hover:shadow-md transition-shadow duration-300 ${className}`} | ||
| bodyStyle={{ | ||
| padding: '20px', | ||
| height: height - 70, // Subtract header height | ||
| overflow: 'hidden' | ||
| }} | ||
| > | ||
| <div className="w-full h-full"> | ||
| {children} | ||
| </div> | ||
| </Card> | ||
| ); | ||
| }; | ||
|
|
||
| ChartCard.propTypes = { | ||
| title: PropTypes.string, | ||
| children: PropTypes.node.isRequired, | ||
| extra: PropTypes.node, | ||
| height: PropTypes.number, | ||
| loading: PropTypes.bool, | ||
| className: PropTypes.string, | ||
| }; | ||
|
|
||
| export default ChartCard; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The editor change handler is called directly in the
onChangecallback, which triggers on every keystroke. This approach bypasses the debouncing logic defined in the useEffect hook (lines 85-112). Consider removing the direct call here and relying solely on the debounced subscription to avoid performance issues with rapid state updates.