Repository: htse1910/HouseKeeperConnect
Pull request #438: New UI fixes: Admin Create Staff, Admin Serivce Page fixes
Author: login: TuanLam123456
Created: 2025-05-25T09:23:53Z | Merged: 2025-05-25T09:26:04Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Introduced an admin interface for managing staff, including a modal form to add new staff members with details such as name, email, password, phone number, and gender.
  - Added a new navigation item labeled "Quản lý nhân viên" to the admin sidebar for easy access to staff management.

- **Enhancements**
  - Updated the label for the service price field to "Giá theo giờ" in both the service list and the add service form for improved clarity.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/FE/home-keeping-website/src/components/AdminSidebar.jsx b/FE/home-keeping-website/src/components/AdminSidebar.jsx
index 27d7a27a..1f08e30b 100644
--- a/FE/home-keeping-website/src/components/AdminSidebar.jsx
+++ b/FE/home-keeping-website/src/components/AdminSidebar.jsx
@@ -10,6 +10,7 @@ const AdminSidebar = () => {
     { path: "/admin/users", icon: <FaTable />, label: "Bảng Tài Khoản" },
     { path: "/admin/services", icon: <FaTools />, label: "Dịch vụ" },
     { path: "/admin/platform-fees", icon: <FaUserShield />, label: "Phí nền tảng" },
+    { path: "/admin/manage-staffs", icon: <FaUserShield />, label: "Quản lý nhân viên" },
   ];
 
   return (
diff --git a/FE/home-keeping-website/src/pages/AdminManageStaffPage.jsx b/FE/home-keeping-website/src/pages/AdminManageStaffPage.jsx
new file mode 100644
index 00000000..3acbdb73
--- /dev/null
+++ b/FE/home-keeping-website/src/pages/AdminManageStaffPage.jsx
@@ -0,0 +1,131 @@
+import React, { useState } from "react";
+import AdminSidebar from "../components/AdminSidebar";
+import { Modal, Button, Form } from "react-bootstrap";
+import axios from "axios";
+import API_BASE_URL from "../config/apiConfig";
+import { toast, ToastContainer } from "react-toastify";
+import "react-toastify/dist/ReactToastify.css";
+
+const AdminManageStaffPage = () => {
+  const [showModal, setShowModal] = useState(false);
+  const [formData, setFormData] = useState({
+    name: "",
+    email: "",
+    password: "",
+    phone: "",
+    gender: 1,
+  });
+
+  const handleChange = (e) => {
+    const { name, value } = e.target;
+    setFormData({ ...formData, [name]: value });
+  };
+
+  const handleSubmit = async () => {
+    try {
+      const token = localStorage.getItem("authToken");
+
+      const form = new FormData();
+      form.append("Name", formData.name);
+      form.append("Email", formData.email);
+      form.append("Password", formData.password);
+      form.append("Phone", formData.phone);
+      form.append("Gender", formData.gender);
+
+      await axios.post(`${API_BASE_URL}/Account/CreateStaff`, form, {
+        headers: {
+          Authorization: `Bearer ${token}`,
+          "Content-Type": "multipart/form-data",
+        },
+      });
+
+      toast.success("Tạo nhân viên thành công!");
+      setShowModal(false);
+      setFormData({ name: "", email: "", password: "", phone: "", gender: 1 });
+    } catch (error) {
+      toast.error("Lỗi khi tạo nhân viên!");
+    }
+  };
+
+  return (
+    <div className="d-flex">
+      <div style={{ width: "250px" }}>
+        <AdminSidebar />
+      </div>
+      <div className="p-4 flex-grow-1">
+        <h3 className="mb-4">Quản Lý Nhân Viên</h3>
+        <Button variant="primary" onClick={() => setShowModal(true)}>
+          Tạo Nhân Viên Mới
+        </Button>
+
+        <Modal show={showModal} onHide={() => setShowModal(false)}>
+          <Modal.Header closeButton>
+            <Modal.Title>Tạo Nhân Viên</Modal.Title>
+          </Modal.Header>
+          <Modal.Body>
+            <Form>
+              <Form.Group className="mb-3">
+                <Form.Label>Tên</Form.Label>
+                <Form.Control
+                  type="text"
+                  name="name"
+                  value={formData.name}
+                  onChange={handleChange}
+                  required
+                />
+              </Form.Group>
+              <Form.Group className="mb-3">
+                <Form.Label>Email</Form.Label>
+                <Form.Control
+                  type="email"
+                  name="email"
+                  value={formData.email}
+                  onChange={handleChange}
+                  required
+                />
+              </Form.Group>
+              <Form.Group className="mb-3">
+                <Form.Label>Mật khẩu</Form.Label>
+                <Form.Control
+                  type="password"
+                  name="password"
+                  value={formData.password}
+                  onChange={handleChange}
+                  required
+                />
+              </Form.Group>
+              <Form.Group className="mb-3">
+                <Form.Label>Số điện thoại</Form.Label>
+                <Form.Control
+                  type="text"
+                  name="phone"
+                  value={formData.phone}
+                  onChange={handleChange}
+                  required
+                />
+              </Form.Group>
+              <Form.Group className="mb-3">
+                <Form.Label>Giới tính</Form.Label>
+                <Form.Select name="gender" value={formData.gender} onChange={handleChange}>
+                  <option value={1}>Nam</option>
+                  <option value={2}>Nữ</option>
+                </Form.Select>
+              </Form.Group>
+            </Form>
+          </Modal.Body>
+          <Modal.Footer>
+            <Button variant="secondary" onClick={() => setShowModal(false)}>
+              Đóng
+            </Button>
+            <Button variant="primary" onClick={handleSubmit}>
+              Tạo
+            </Button>
+          </Modal.Footer>
+        </Modal>
+        <ToastContainer />
+      </div>
+    </div>
+  );
+};
+
+export default AdminManageStaffPage;
\ No newline at end of file
diff --git a/FE/home-keeping-website/src/pages/AdminServicePgae.jsx b/FE/home-keeping-website/src/pages/AdminServicePgae.jsx
index 978e0020..1f90855b 100644
--- a/FE/home-keeping-website/src/pages/AdminServicePgae.jsx
+++ b/FE/home-keeping-website/src/pages/AdminServicePgae.jsx
@@ -141,7 +141,7 @@ const AdminServiceListPage = () => {
                       <th>ID</th>
                       <th>Tên dịch vụ</th>
                       <th>Loại dịch vụ</th>
-                      <th>Giá</th>
+                      <th>Giá theo giờ</th>
                       <th>Mô tả</th>
                       <th>Trạng thái</th>
                       <th>Chỉnh sửa</th>
@@ -306,7 +306,7 @@ const AdminServiceListPage = () => {
               />
             </Form.Group>
             <Form.Group className="mb-3">
-              <Form.Label>Giá</Form.Label>
+              <Form.Label>Giá theo giờ</Form.Label>
               <Form.Control
                 type="number"
                 value={newService.price}
diff --git a/FE/home-keeping-website/src/routes.jsx b/FE/home-keeping-website/src/routes.jsx
index 539b8016..872a2457 100644
--- a/FE/home-keeping-website/src/routes.jsx
+++ b/FE/home-keeping-website/src/routes.jsx
@@ -64,6 +64,7 @@ import StaffTransactionListPage from './pages/StaffTransactionListPage.jsx';
 import FamilyAbandonedJobsPage from './pages/FamilyAbandonedJobsPage.jsx';
 import CreateDirectJobOfferPage from './pages/CreateDirectJobOfferPage.jsx';
 import HousekeeperSchedulePage from './pages/HousekeeperSchedulePage.jsx';
+import AdminManageStaffPage from './pages/AdminManageStaffPage.jsx';
 
 function AppRoutes() {
   return (
@@ -138,8 +139,9 @@ function AppRoutes() {
         {/* Admin Pages */}
         <Route path="/admin/dashboard" element={<AdminDashboardPage />} />
         <Route path="/admin/users" element={<AdminUserListPage />} />
-        <Route path="/admin/services" element={<AdminServiceListPage />} />  // ✅ NEW SERVICE PAGE
+        <Route path="/admin/services" element={<AdminServiceListPage />} />  
         <Route path="/admin/platform-fees" element={<ManagePlatformFeesPage />} />
+        <Route path="/admin/manage-staffs" element={<AdminManageStaffPage />} />
       </Routes>
     </Layout>
   );


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>FE/home-keeping-website/src/components/AdminSidebar.jsx (1)</summary><blockquote>

`13-13`: **Consider using a different icon for better visual differentiation.**

The new staff management navigation item uses the same `FaUserShield` icon as the "Phí nền tảng" (Platform Fees) item above it. This could confuse users when navigating.



Consider using a more specific icon for staff management:

```diff
-    { path: "/admin/manage-staffs", icon: <FaUserShield />, label: "Quản lý nhân viên" },
+    { path: "/admin/manage-staffs", icon: <FaUsers />, label: "Quản lý nhân viên" },
```

Don't forget to import `FaUsers` from react-icons/fa.

</blockquote></details>
<details>
<summary>FE/home-keeping-website/src/pages/AdminServicePgae.jsx (1)</summary><blockquote>

`1-1`: **Fix filename typo.**

The filename "AdminServicePgae.jsx" contains a typo - it should be "AdminServicePage.jsx".

</blockquote></details>
<details>
<summary>FE/home-keeping-website/src/pages/AdminManageStaffPage.jsx (3)</summary><blockquote>

`28-34`: **Consider using JSON instead of FormData.**

Using FormData for simple text fields is unnecessary complexity. The backend likely accepts JSON, which would be more standard for this type of data.



```diff
-      const form = new FormData();
-      form.append("Name", formData.name);
-      form.append("Email", formData.email);
-      form.append("Password", formData.password);
-      form.append("Phone", formData.phone);
-      form.append("Gender", formData.gender);

-      await axios.post(`${API_BASE_URL}/Account/CreateStaff`, form, {
+      await axios.post(`${API_BASE_URL}/Account/CreateStaff`, {
+        Name: formData.name,
+        Email: formData.email,
+        Password: formData.password,
+        Phone: formData.phone,
+        Gender: formData.gender
+      }, {
         headers: {
           Authorization: `Bearer ${token}`,
-          "Content-Type": "multipart/form-data",
+          "Content-Type": "application/json",
         },
       });
```

---

`109-112`: **Replace magic numbers with constants.**

Using magic numbers (1, 2) for gender values reduces code readability and maintainability.



```diff
+const GENDER_OPTIONS = {
+  MALE: 1,
+  FEMALE: 2
+};

                <Form.Select name="gender" value={formData.gender} onChange={handleChange}>
-                  <option value={1}>Nam</option>
-                  <option value={2}>Nữ</option>
+                  <option value={GENDER_OPTIONS.MALE}>Nam</option>
+                  <option value={GENDER_OPTIONS.FEMALE}>Nữ</option>
                </Form.Select>
```

Also update the initial state:

```diff
   const [formData, setFormData] = useState({
     name: "",
     email: "",
     password: "",
     phone: "",
-    gender: 1,
+    gender: GENDER_OPTIONS.MALE,
   });
```

---

`61-61`: **Add confirmation dialog for unsaved changes.**

Consider adding a confirmation dialog when closing the modal if the user has entered data, to prevent accidental loss of input.



```diff
+  const hasUnsavedChanges = formData.name || formData.email || formData.password || formData.phone;
+
+  const handleCloseModal = () => {
+    if (hasUnsavedChanges) {
+      if (window.confirm("Bạn có chắc chắn muốn đóng? Dữ liệu đã nhập sẽ bị mất.")) {
+        setShowModal(false);
+        setFormData({ name: "", email: "", password: "", phone: "", gender: 1 });
+      }
+    } else {
+      setShowModal(false);
+    }
+  };

-        <Modal show={showModal} onHide={() => setShowModal(false)}>
+        <Modal show={showModal} onHide={handleCloseModal}>
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 1d3b481371b5a792620aa16faebcaf1fce25c851 and 49e00d52b4de1849957ffdbb00e1652e34c2de8e.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `FE/home-keeping-website/src/components/AdminSidebar.jsx` (1 hunks)
* `FE/home-keeping-website/src/pages/AdminManageStaffPage.jsx` (1 hunks)
* `FE/home-keeping-website/src/pages/AdminServicePgae.jsx` (2 hunks)
* `FE/home-keeping-website/src/routes.jsx` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>FE/home-keeping-website/src/routes.jsx (1)</summary><blockquote>

<details>
<summary>FE/home-keeping-website/src/pages/AdminManageStaffPage.jsx (1)</summary>

* `AdminManageStaffPage` (9-129)

</details>

</blockquote></details>
<details>
<summary>FE/home-keeping-website/src/pages/AdminManageStaffPage.jsx (2)</summary><blockquote>

<details>
<summary>FE/home-keeping-website/src/config/apiConfig.jsx (1)</summary>

* `API_BASE_URL` (1-1)

</details>
<details>
<summary>FE/home-keeping-website/src/components/AdminSidebar.jsx (1)</summary>

* `AdminSidebar` (5-41)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>FE/home-keeping-website/src/pages/AdminServicePgae.jsx (1)</summary>

`144-144`: **LGTM! Improved label clarity.**

The change from "Giá" to "Giá theo giờ" (Price to Price per hour) improves clarity for users by explicitly indicating the pricing unit.




Also applies to: 309-309

</details>
<details>
<summary>FE/home-keeping-website/src/routes.jsx (1)</summary>

`67-67`: **LGTM! Route configuration is correct.**

The new route for staff management is properly configured and follows the established pattern for admin routes. The path `/admin/manage-staffs` matches the navigation item in the sidebar.




Also applies to: 144-144

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-25T09:26:18Z

Review comments:
diff_hunk: @@ -0,0 +1,131 @@
+import React, { useState } from "react";
+import AdminSidebar from "../components/AdminSidebar";
+import { Modal, Button, Form } from "react-bootstrap";
+import axios from "axios";
+import API_BASE_URL from "../config/apiConfig";
+import { toast, ToastContainer } from "react-toastify";
+import "react-toastify/dist/ReactToastify.css";
+
+const AdminManageStaffPage = () => {
+  const [showModal, setShowModal] = useState(false);
+  const [formData, setFormData] = useState({
+    name: "",
+    email: "",
+    password: "",
+    phone: "",
+    gender: 1,
+  });
+
+  const handleChange = (e) => {
+    const { name, value } = e.target;
+    setFormData({ ...formData, [name]: value });
+  };
+
+  const handleSubmit = async () => {
+    try {
+      const token = localStorage.getItem("authToken");
+
+      const form = new FormData();
+      form.append("Name", formData.name);
+      form.append("Email", formData.email);
+      form.append("Password", formData.password);
+      form.append("Phone", formData.phone);
+      form.append("Gender", formData.gender);
+
+      await axios.post(`${API_BASE_URL}/Account/CreateStaff`, form, {
+        headers: {
+          Authorization: `Bearer ${token}`,
+          "Content-Type": "multipart/form-data",
+        },
+      });
+
+      toast.success("Tạo nhân viên thành công!");
+      setShowModal(false);
+      setFormData({ name: "", email: "", password: "", phone: "", gender: 1 });
+    } catch (error) {
+      toast.error("Lỗi khi tạo nhân viên!");
+    }
+  };
+
+  return (
+    <div className="d-flex">
+      <div style={{ width: "250px" }}>
+        <AdminSidebar />
+      </div>
+      <div className="p-4 flex-grow-1">
+        <h3 className="mb-4">Quản Lý Nhân Viên</h3>
+        <Button variant="primary" onClick={() => setShowModal(true)}>
+          Tạo Nhân Viên Mới
+        </Button>
+
+        <Modal show={showModal} onHide={() => setShowModal(false)}>
+          <Modal.Header closeButton>
+            <Modal.Title>Tạo Nhân Viên</Modal.Title>
+          </Modal.Header>
+          <Modal.Body>
+            <Form>
+              <Form.Group className="mb-3">
+                <Form.Label>Tên</Form.Label>
+                <Form.Control
+                  type="text"
+                  name="name"
+                  value={formData.name}
+                  onChange={handleChange}
+                  required
+                />
+              </Form.Group>
+              <Form.Group className="mb-3">
+                <Form.Label>Email</Form.Label>
+                <Form.Control
+                  type="email"
+                  name="email"
+                  value={formData.email}
+                  onChange={handleChange}
+                  required
+                />
+              </Form.Group>
+              <Form.Group className="mb-3">
+                <Form.Label>Mật khẩu</Form.Label>
+                <Form.Control
+                  type="password"
+                  name="password"
+                  value={formData.password}
+                  onChange={handleChange}
+                  required
+                />
+              </Form.Group>
+              <Form.Group className="mb-3">
+                <Form.Label>Số điện thoại</Form.Label>
+                <Form.Control
+                  type="text"
+                  name="phone"
+                  value={formData.phone}
+                  onChange={handleChange}
+                  required
+                />
+              </Form.Group>
+              <Form.Group className="mb-3">
+                <Form.Label>Giới tính</Form.Label>
+                <Form.Select name="gender" value={formData.gender} onChange={handleChange}>
+                  <option value={1}>Nam</option>
+                  <option value={2}>Nữ</option>
+                </Form.Select>
+              </Form.Group>
+            </Form>
+          </Modal.Body>
+          <Modal.Footer>
+            <Button variant="secondary" onClick={() => setShowModal(false)}>
+              Đóng
+            </Button>
+            <Button variant="primary" onClick={handleSubmit}>
+              Tạo
+            </Button>
path: FE/home-keeping-website/src/pages/AdminManageStaffPage.jsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Add loading state to submit button.**

The submit button should be disabled during API calls to prevent multiple submissions.



```diff
-            <Button variant="primary" onClick={handleSubmit}>
+            <Button 
+              variant="primary" 
+              onClick={handleSubmit}
+              disabled={loading}
+            >
+              {loading ? "Đang tạo..." : "Tạo"}
-              Tạo
             </Button>
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
            <Button
              variant="primary"
              onClick={handleSubmit}
              disabled={loading}
            >
              {loading ? "Đang tạo..." : "Tạo"}
            </Button>
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In FE/home-keeping-website/src/pages/AdminManageStaffPage.jsx around lines 120
to 122, the submit button does not handle a loading state, allowing multiple
submissions during API calls. Introduce a loading state variable in the
component's state to track when the API call is in progress. Disable the Button
component and optionally show a loading indicator while this state is true, and
reset the loading state once the API call completes or fails.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-25T09:26:17Z
author_association: NONE
diff_hunk: @@ -0,0 +1,131 @@
+import React, { useState } from "react";
+import AdminSidebar from "../components/AdminSidebar";
+import { Modal, Button, Form } from "react-bootstrap";
+import axios from "axios";
+import API_BASE_URL from "../config/apiConfig";
+import { toast, ToastContainer } from "react-toastify";
+import "react-toastify/dist/ReactToastify.css";
+
+const AdminManageStaffPage = () => {
+  const [showModal, setShowModal] = useState(false);
+  const [formData, setFormData] = useState({
+    name: "",
+    email: "",
+    password: "",
+    phone: "",
+    gender: 1,
+  });
+
+  const handleChange = (e) => {
+    const { name, value } = e.target;
+    setFormData({ ...formData, [name]: value });
+  };
+
+  const handleSubmit = async () => {
+    try {
+      const token = localStorage.getItem("authToken");
+
+      const form = new FormData();
+      form.append("Name", formData.name);
+      form.append("Email", formData.email);
+      form.append("Password", formData.password);
+      form.append("Phone", formData.phone);
+      form.append("Gender", formData.gender);
+
+      await axios.post(`${API_BASE_URL}/Account/CreateStaff`, form, {
+        headers: {
+          Authorization: `Bearer ${token}`,
+          "Content-Type": "multipart/form-data",
+        },
+      });
+
+      toast.success("Tạo nhân viên thành công!");
+      setShowModal(false);
+      setFormData({ name: "", email: "", password: "", phone: "", gender: 1 });
+    } catch (error) {
+      toast.error("Lỗi khi tạo nhân viên!");
+    }
+  };
path: FE/home-keeping-website/src/pages/AdminManageStaffPage.jsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Enhance form validation and error handling.**

The form submission has several areas for improvement:

1. **Missing client-side validation** - No checks for required fields before API call
2. **Generic error handling** - All errors show the same message regardless of the actual issue
3. **No loading state** - Users could submit multiple times during API call



Consider this improved implementation:

```diff
+  const [loading, setLoading] = useState(false);

+  const validateForm = () => {
+    if (!formData.name.trim()) {
+      toast.error("Tên không được để trống!");
+      return false;
+    }
+    if (!formData.email.trim()) {
+      toast.error("Email không được để trống!");
+      return false;
+    }
+    if (!formData.password.trim()) {
+      toast.error("Mật khẩu không được để trống!");
+      return false;
+    }
+    if (!formData.phone.trim()) {
+      toast.error("Số điện thoại không được để trống!");
+      return false;
+    }
+    return true;
+  };

   const handleSubmit = async () => {
+    if (!validateForm()) return;
+    
+    setLoading(true);
     try {
       // ... existing code ...
     } catch (error) {
+      console.error("Error creating staff:", error);
+      if (error.response?.status === 409) {
+        toast.error("Email đã tồn tại!");
+      } else if (error.response?.data) {
+        toast.error(error.response.data);
+      } else {
-        toast.error("Lỗi khi tạo nhân viên!");
+        toast.error("Lỗi khi tạo nhân viên! Vui lòng thử lại.");
+      }
+    } finally {
+      setLoading(false);
     }
   };
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
  // Add loading state
  const [loading, setLoading] = useState(false);

  // Client-side form validation
  const validateForm = () => {
    if (!formData.name.trim()) {
      toast.error("Tên không được để trống!");
      return false;
    }
    if (!formData.email.trim()) {
      toast.error("Email không được để trống!");
      return false;
    }
    if (!formData.password.trim()) {
      toast.error("Mật khẩu không được để trống!");
      return false;
    }
    if (!formData.phone.trim()) {
      toast.error("Số điện thoại không được để trống!");
      return false;
    }
    return true;
  };

  const handleSubmit = async () => {
    // Prevent submission if validation fails
    if (!validateForm()) return;

    setLoading(true);
    try {
      const token = localStorage.getItem("authToken");

      const form = new FormData();
      form.append("Name", formData.name);
      form.append("Email", formData.email);
      form.append("Password", formData.password);
      form.append("Phone", formData.phone);
      form.append("Gender", formData.gender);

      await axios.post(
        `${API_BASE_URL}/Account/CreateStaff`,
        form,
        {
          headers: {
            Authorization: `Bearer ${token}`,
            "Content-Type": "multipart/form-data",
          },
        }
      );

      toast.success("Tạo nhân viên thành công!");
      setShowModal(false);
      setFormData({ name: "", email: "", password: "", phone: "", gender: 1 });
    } catch (error) {
      console.error("Error creating staff:", error);
      if (error.response?.status === 409) {
        toast.error("Email đã tồn tại!");
      } else if (error.response?.data) {
        toast.error(error.response.data);
      } else {
        toast.error("Lỗi khi tạo nhân viên! Vui lòng thử lại.");
      }
    } finally {
      setLoading(false);
    }
  };
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In FE/home-keeping-website/src/pages/AdminManageStaffPage.jsx around lines 24 to
48, enhance the handleSubmit function by adding client-side validation to check
required fields before making the API call, implement a loading state to prevent
multiple submissions during the request, and improve error handling by capturing
and displaying specific error messages instead of a generic one. Update the
state to track loading, validate formData fields, conditionally proceed with the
API call only if validation passes, and in the catch block, extract and show
detailed error information in the toast message.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-25T09:26:17Z
author_association: NONE

PR comments:
user: login: vercel[bot]
created_at: 2025-05-25T09:23:57Z
body: [vc]: #YLua9UR1l/AfjZHK82BTwfkwRk26wFpSCPrO0ML0AlM=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJob3VzZWtlZXBlci1jb25uZWN0IiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2hvYW5nLWR1b25ncy1wcm9qZWN0cy1kMjU4NjYxNS9ob3VzZWtlZXBlci1jb25uZWN0L1F4dmdTNmFVZzJxajZRUnk2VENlamZaZTdLM2kiLCJwcmV2aWV3VXJsIjoiaG91c2VrZWVwZXItY29ubmVjdC1naXQtbGFtLTEtaG9hbmctZHVvbmdzLXByb2plY3RzLWQyNTg2NjE1LnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoiaG91c2VrZWVwZXItY29ubmVjdC1naXQtbGFtLTEtaG9hbmctZHVvbmdzLXByb2plY3RzLWQyNTg2NjE1LnZlcmNlbC5hcHAifSwicm9vdERpcmVjdG9yeSI6IkZFL2hvbWUta2VlcGluZy13ZWJzaXRlIn1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **housekeeper-connect** | ✅ Ready ([Inspect](https://vercel.com/hoang-duongs-projects-d2586615/housekeeper-connect/QxvgS6aUg2qj6QRy6TCejfZe7K3i)) | [Visit Preview](https://housekeeper-connect-git-lam-1-hoang-duongs-projects-d2586615.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/housekeeper-connect-git-lam-1-hoang-duongs-projects-d2586615.vercel.app?via=pr-comment-feedback-link) | May 25, 2025 9:24am |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-05-25T09:23:59Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

A new admin staff management page and route were added to the application, including a sidebar navigation link. The new page provides a modal form for creating staff accounts, with form handling and API integration. Additionally, the label for service pricing was updated in the admin service page.

## Changes

| File(s)                                                                 | Change Summary                                                                                      |
|-------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
| `.../components/AdminSidebar.jsx`                                       | Added a new sidebar navigation link for "Quản lý nhân viên" (Manage Staffs) with appropriate icon.  |
| `.../pages/AdminManageStaffPage.jsx`                                    | Introduced new `AdminManageStaffPage` component with staff creation modal and form handling logic.  |
| `.../pages/AdminServicePgae.jsx`                                        | Updated "Giá" label to "Giá theo giờ" in table header and modal form.                               |
| `.../routes.jsx`                                                        | Added import and route for `AdminManageStaffPage` at `/admin/manage-staffs`.                        |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Admin as Admin User
    participant Sidebar as AdminSidebar
    participant Router as AppRoutes
    participant StaffPage as AdminManageStaffPage
    participant API as Backend API

    Admin->>Sidebar: Clicks "Quản lý nhân viên"
    Sidebar->>Router: Navigates to "/admin/manage-staffs"
    Router->>StaffPage: Renders AdminManageStaffPage
    Admin->>StaffPage: Clicks "Thêm nhân viên" (Add Staff)
    StaffPage->>Admin: Shows modal form
    Admin->>StaffPage: Fills and submits form
    StaffPage->>API: POST /Account/CreateStaff with form data
    API-->>StaffPage: Returns success or error
    StaffPage->>Admin: Shows toast notification
```

## Poem

> In the warren of admin, a new door appears,  
> For staff to be managed—three bunny cheers!  
> A modal pops up, with fields to complete,  
> Toasts for success, making the task sweet.  
> And now, by the hour, service prices are clear—  
> Hopping forward, improvements draw near!  
> 🐇✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=htse1910/HouseKeeperConnect&utm_content=438):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE