Skip to content

feat(FI-08): Add legal document#13

Open
PHUOBG wants to merge 2 commits into
devfrom
feat/FI-08
Open

feat(FI-08): Add legal document#13
PHUOBG wants to merge 2 commits into
devfrom
feat/FI-08

Conversation

@PHUOBG

@PHUOBG PHUOBG commented May 4, 2026

Copy link
Copy Markdown
Collaborator

UC-5: Add legal document
[FI-08] Tạo UI của UC5 đến UC7
( PHƯƠNG(Frontend)

@PhamVanTien1910 PhamVanTien1910 changed the title UC-5-Sprint_3: Add legal document feat(FI-08): Add legal document May 4, 2026
Comment on lines +1 to +128
import { Bell, User, Search } from "lucide-react";
import { Sidebar } from "./Sidebar";
import { AddDocumentForm } from "./AddDocumentForm";
import { Toast } from "./Toast";
import { useToast } from "../hooks/useToast";

/**
* Full-page layout: sidebar (left) + content area (right).
* Occupies the full viewport height.
*/
export function AddDocumentPage() {
const toast = useToast();

return (
<div
style={{
display: "flex",
height: "100vh",
overflow: "hidden",
fontFamily:
"'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif",
}}
>
{/* Left sidebar */}
<Sidebar />

{/* Right content */}
<main
style={{
flex: 1,
background: "#f5f6f8",
padding: "28px 32px",
overflowY: "auto",
}}
>
{/* Top bar */}
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "24px",
}}
>
{/* Search */}
<div style={{ position: "relative" }}>
<Search
size={14}
color="#9ca3af"
style={{
position: "absolute",
left: "10px",
top: "50%",
transform: "translateY(-50%)",
}}
/>
<input
type="search"
placeholder="Search legal templates, statutes, or case laws..."
style={{
background: "#fff",
border: "1px solid #e5e7eb",
borderRadius: "6px",
height: "34px",
fontSize: "13px",
width: "280px",
paddingLeft: "32px",
paddingRight: "12px",
outline: "none",
color: "#374151",
boxSizing: "border-box",
}}
/>
</div>

{/* Right icons */}
<div style={{ display: "flex", alignItems: "center", gap: "16px" }}>
<Bell size={18} color="#6b7280" style={{ cursor: "pointer" }} />
<User size={18} color="#6b7280" style={{ cursor: "pointer" }} />
</div>
</div>

{/* Form card */}
<div
style={{
background: "#ffffff",
borderRadius: "12px",
border: "1px solid #e5e7eb",
padding: "28px 28px 24px",
}}
>
{/* Card header */}
<h1
style={{
fontSize: "22px",
fontWeight: 700,
color: "#1e3a8a",
margin: "0 0 4px",
fontFamily: "'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif",
letterSpacing: "-0.01em",
}}
>
Thêm văn bản pháp luật
</h1>
<p
style={{
fontSize: "13px",
color: "#6b7280",
margin: "0 0 24px",
}}
>
Nhập thông tin văn bản để phục vụ tra cứu và AI phân tích
</p>

<AddDocumentForm onSuccess={toast.show} />
</div>
</main>

{/* Toast notification */}
<Toast
visible={toast.visible}
entering={toast.entering}
exiting={toast.exiting}
onDismiss={toast.dismiss}
/>
</div>
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cái này page thì phải nằm ở folder page chứ em, em move qua nha

Comment on lines +1 to +136
import { useState } from "react";
import { Bell, Search } from "lucide-react";
import { Sidebar } from "./Sidebar";
import { FilterBar } from "./FilterBar";
import { DocumentTable } from "./DocumentTable";
import { MOCK_DOCUMENTS, LegalDocument } from "../constants/mockDocuments";

/**
* UC-6: Legal Document Management (Admin) page.
* Displays a searchable, filterable list of legal documents
* with an expandable version history per row.
*/
export function DocumentListPage() {
const [documents, setDocuments] = useState<LegalDocument[]>(MOCK_DOCUMENTS);
const [search, setSearch] = useState("");

const filtered = documents.filter(
(d) =>
d.tenVanBan.toLowerCase().includes(search.toLowerCase()) ||
d.soHieu.toLowerCase().includes(search.toLowerCase())
);

const handleToggle = (id: number) => {
setDocuments((prev) =>
prev.map((d) => (d.id === id ? { ...d, active: !d.active } : d))
);
};

return (
<div
style={{
display: "flex",
height: "100vh",
overflow: "hidden",
fontFamily: "'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif",
}}
>
{/* Left sidebar */}
<Sidebar />

{/* Right content */}
<main
style={{
flex: 1,
background: "#f5f6f8",
padding: "28px 32px",
overflowY: "auto",
}}
>
{/* Top bar */}
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "24px",
}}
>
<div style={{ position: "relative" }}>
<Search
size={14}
color="#9ca3af"
style={{
position: "absolute",
left: "10px",
top: "50%",
transform: "translateY(-50%)",
}}
/>
<input
type="search"
placeholder="Search legal templates, statutes, or case laws..."
style={{
background: "#fff",
border: "1px solid #e5e7eb",
borderRadius: "6px",
height: "34px",
fontSize: "13px",
width: "280px",
paddingLeft: "32px",
paddingRight: "12px",
outline: "none",
color: "#374151",
boxSizing: "border-box",
}}
/>
</div>
<div style={{ display: "flex", alignItems: "center", gap: "16px" }}>
<Bell size={18} color="#6b7280" />
</div>
</div>

{/* Page heading */}
<h1
style={{
fontSize: "22px",
fontWeight: 700,
color: "#1a2d6b",
margin: "0 0 2px",
letterSpacing: "-0.02em",
lineHeight: 1.25,
fontFamily: "'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif",
}}
>
Quản lý văn bản pháp luật
</h1>
<p
style={{
fontSize: "13px",
color: "#6b7280",
margin: "0 0 20px",
}}
>
Cập nhật và điều chỉnh các văn bản pháp quy trong hệ thống.
</p>

{/* White card */}
<div
style={{
background: "#fff",
borderRadius: "8px",
border: "1px solid #e5e7eb",
overflow: "hidden",
}}
>
<div style={{ padding: "12px 16px", borderBottom: "1px solid #f3f4f6" }}>
<FilterBar search={search} onSearchChange={setSearch} />
</div>

{/* Document table */}
<DocumentTable documents={filtered} onToggle={handleToggle} />
</div>
</main>
</div>
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

này nó cũng là page nên em move qua folder page nha

}
`}</style>
</form>
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

form thì e tạo một folder form rồi e move nào, component này mục địch là tách phần nhỏ để tái sử dụng e

type="text"
aria-invalid={!!errors.soHieuVanBan}
data-error={!!errors.soHieuVanBan}
{...register("soHieuVanBan", {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A thấy e nên lưu register("soHieuVanBan") vào một biến thay vì e gọi nhiều lần để nó improve readability vs maintainability. Vì hiện tại em đang define handler ở 2 chỗ cho cùng một field:
{...register("soHieuVanBan", {
onBlur: (e) => checkDuplicate(e.target.value),
})}

onChange={(e) => {
register("soHieuVanBan").onChange(e);
clearDuplicate();
}}

Em refactor kiểu này nhìn nó clean hơn nè:

const soHieuField = register("soHieuVanBan", {
onBlur: (e) => checkDuplicate(e.target.value),
});

<input
{...soHieuField}
onChange={(e) => {
soHieuField.onChange(e);
clearDuplicate();
}}
/>

overflowY: "auto",
}}
>
{/* Top bar */}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A thấy phần layout đang bị duplicate giữa các page, đoạn Sidebar, Topbar với phần wrapper main. Mấy phần này có thể tách ra thành một dashboar layout riêng để nó tránh lặp code vs dễ maintain hơn khi sau này cần update UI, layout chung

}}
/>
<input
type="search"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

này chưa hoạt đồng nè em, input ni ko có value ko có onchange

<tbody>
{documents.map((doc) => (
<>
<tr key={doc.id} style={{ background: "#fff" }}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A thấy đoạn documents.map() này đang dùng fragment nhưng chưa có key ở root element:

{documents.map((doc) => (
<>

Trong React thì key em nên đặt ở phần tử root của vòng map để reconciliation hoạt động ổn định hơn á với tránh warning về sau. Em sửa như ri nè:

{documents.map((doc) => (
<React.Fragment key={doc.id}>

hoặc ri

@PhamVanTien1910 PhamVanTien1910 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Em move từ inline styling qua tailwind nha
  2. Chia component để tái sử dụng, tại vì anh thấy code e có nhiều cái lặp nhau với có thể tách component được
  3. Fix những cái anh review chừng đó trước. Rồi đẩy lên cho anh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants