Step 1: สร้างโปรเจกต์ React ด้วย Vite Terminal
เป้าหมาย: สร้างโปรเจกต์ชื่อ coreui-react แล้วรันให้เห็นหน้าเริ่มต้น
npm create vite@latest coreui-react -- --template react
cd coreui-react
npm install
npm run dev
Step 2: ติดตั้ง CoreUI + Router + Icons Terminal
ติดตั้ง UI components, Router และไอคอน เพื่อทำหน้า Admin ให้ดูมืออาชีพขึ้น
npm i @coreui/react @coreui/coreui @popperjs/core
npm i react-router-dom
npm i bootstrap-icons
Step 3: Import CSS/JS ใน main.jsx src/main.jsx
Import CoreUI CSS/JS + Bootstrap Icons + Global CSS ของเรา
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
// CoreUI
import '@coreui/coreui/dist/css/coreui.min.css'
import '@coreui/coreui/dist/js/coreui.bundle.min.js'
// Bootstrap Icons
import 'bootstrap-icons/font/bootstrap-icons.css'
// Global CSS ของเรา
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
Step 4: จัดโครงสร้างโฟลเดอร์ Project Structure
แยก Layout / Pages / Routes เพื่อให้อ่านง่ายและขยายต่อได้
src/
layout/
AppLayout.jsx
AppHeader.jsx
AppSidebar.jsx
pages/
Dashboard.jsx
Users.jsx
routes/
AppRoutes.jsx
index.css
App.jsx
main.jsx
Step 5: สร้าง index.css (Sidebar UX) src/index.css
ทำเมนูให้ดูเป็น list ปกติ, active/hover สวย, และตอนย่อ sidebar ให้ซ่อน label
.sidebar-nav .nav-link{
padding: .55rem .75rem;
border-radius: .5rem;
line-height: 1.2;
}
.sidebar-nav .nav-link:hover{
background: #f6f8fb;
}
.sidebar-nav .nav-link.active{
background: #eef2ff;
color: #1f2a44;
font-weight: 600;
border-left: 3px solid #4f46e5;
}
.sidebar.sidebar-narrow .nav-label{
display:none;
}
Step 6: สร้าง Pages (Dashboard/Users) src/pages/*
สร้างหน้าเพื่อลองสลับ route และทำ Dashboard เป็น KPI Cards + Activities
import { CCard, CCardBody } from "@coreui/react";
export default function Users() {
return (
<CCard className="shadow-sm border-0 rounded-4">
<CCardBody className="p-4">
<h5 className="fw-semibold mb-2">Users</h5>
<div className="text-body-secondary">
หน้านี้เตรียมไว้สำหรับทำ Table + Search + Pagination
</div>
</CCardBody>
</CCard>
);
}
import {
CCard,
CCardBody,
CCardHeader,
CRow,
CCol,
CProgress,
CTable,
CTableHead,
CTableRow,
CTableHeaderCell,
CTableBody,
CTableDataCell,
CBadge,
} from "@coreui/react";
function StatCard({ title, value, delta, icon, hint, progress }) {
return (
<CCard className="shadow-sm border-0 rounded-4 h-100">
<CCardBody className="p-3 p-md-4">
<div className="d-flex align-items-start justify-content-between">
<div>
<div className="text-body-secondary small">{title}</div>
<div className="fs-4 fw-semibold mt-1">{value}</div>
<div className="small mt-2">
<CBadge color={delta.startsWith("+") ? "success" : "danger"} className="me-2">
{delta}
</CBadge>
<span className="text-body-secondary">{hint}</span>
</div>
</div>
<div
className="rounded-4 d-inline-flex align-items-center justify-content-center bg-light"
style={{ width: 44, height: 44 }}
>
<i className={`bi ${icon} fs-5`} />
</div>
</div>
{typeof progress === "number" && (
<div className="mt-3">
<CProgress value={progress} />
<div className="small text-body-secondary mt-1">{progress}% of target</div>
</div>
)}
</CCardBody>
</CCard>
);
}
export default function Dashboard() {
return (
<div className="pb-4">
<CRow className="g-3 g-md-4">
<CCol xs={12} sm={6} xl={3}>
<StatCard title="Active Users" value="1,248" delta="+8.2%" icon="bi-people" hint="เทียบสัปดาห์ก่อน" progress={72} />
</CCol>
<CCol xs={12} sm={6} xl={3}>
<StatCard title="Revenue" value="฿ 342,900" delta="+4.6%" icon="bi-cash-stack" hint="รายได้เดือนนี้" progress={58} />
</CCol>
<CCol xs={12} sm={6} xl={3}>
<StatCard title="New Orders" value="392" delta="-1.3%" icon="bi-bag-check" hint="คำสั่งซื้อใหม่" progress={41} />
</CCol>
<CCol xs={12} sm={6} xl={3}>
<StatCard title="System Health" value="99.9%" delta="+0.1%" icon="bi-shield-check" hint="Uptime 30 วัน" progress={99} />
</CCol>
</CRow>
<CRow className="g-3 g-md-4 mt-1">
<CCol lg={7}>
<CCard className="shadow-sm border-0 rounded-4 h-100">
<CCardHeader className="bg-white border-0 pt-4 px-4">
<div className="d-flex align-items-center justify-content-between">
<div className="fw-semibold">Recent Activities</div>
<span className="small text-body-secondary">Updated just now</span>
</div>
</CCardHeader>
<CCardBody className="px-4 pb-4">
<CTable responsive className="align-middle mb-0">
<CTableHead>
<CTableRow>
<CTableHeaderCell>Time</CTableHeaderCell>
<CTableHeaderCell>Action</CTableHeaderCell>
<CTableHeaderCell>Status</CTableHeaderCell>
</CTableRow>
</CTableHead>
<CTableBody>
{[
{ t: "09:12", a: "User login: user_102", s: "success" },
{ t: "09:25", a: "Create order: #A-3921", s: "success" },
{ t: "10:02", a: "Payment pending: #A-3918", s: "warning" },
{ t: "10:18", a: "Failed login attempt", s: "danger" },
].map((r, i) => (
<CTableRow key={i}>
<CTableDataCell className="text-body-secondary">{r.t}</CTableDataCell>
<CTableDataCell>{r.a}</CTableDataCell>
<CTableDataCell>
<CBadge color={r.s}>{r.s.toUpperCase()}</CBadge>
</CTableDataCell>
</CTableRow>
))}
</CTableBody>
</CTable>
</CCardBody>
</CCard>
</CCol>
<CCol lg={5}>
<CCard className="shadow-sm border-0 rounded-4 h-100">
<CCardHeader className="bg-white border-0 pt-4 px-4">
<div className="fw-semibold">Quick Insights</div>
</CCardHeader>
<CCardBody className="px-4 pb-4">
<div className="mb-3">
<div className="d-flex justify-content-between small">
<span className="text-body-secondary">Conversion Rate</span>
<span className="fw-semibold">3.4%</span>
</div>
<CProgress value={34} className="mt-2" />
</div>
<div className="mb-3">
<div className="d-flex justify-content-between small">
<span className="text-body-secondary">Support Tickets</span>
<span className="fw-semibold">18 open</span>
</div>
<CProgress value={18} className="mt-2" />
</div>
<div className="mb-0">
<div className="d-flex justify-content-between small">
<span className="text-body-secondary">Storage Used</span>
<span className="fw-semibold">62%</span>
</div>
<CProgress value={62} className="mt-2" />
</div>
<div className="mt-4 p-3 rounded-4 bg-light">
<div className="fw-semibold">Next step</div>
<div className="text-body-secondary small">
เพิ่มหน้า Reports และทำ Table/Filter เพื่อเป็นงานปลายภาค
</div>
</div>
</CCardBody>
</CCard>
</CCol>
</CRow>
</div>
);
}
Step 7: สร้าง Routes (React Router v6) — Diagram Routing
SPA (Single Page Application) คือเว็บที่ “ไม่ reload ทั้งหน้า” เวลาเปลี่ยนหน้า แต่จะสลับคอมโพเนนต์ตาม URL แทน
แนวคิด: URL เปลี่ยน → Router เลือก Route → แสดงคอมโพเนนต์ในพื้นที่ Content
ต่อไปคือโค้ดประกาศ Route จริง ๆ: สร้างไฟล์ src/routes/AppRoutes.jsx
โดยกำหนดว่า URL ไหน แสดงคอมโพเนนต์อะไร
import { Routes, Route } from "react-router-dom";
import Dashboard from "../pages/Dashboard";
import Users from "../pages/Users";
export default function AppRoutes() {
return (
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/users" element={<Users />} />
</Routes>
);
}
Step 8: สร้าง Sidebar (narrow) src/layout/AppSidebar.jsx
ใช้ <NavLink> render ลิงก์เอง เพื่อให้คลิกเปลี่ยนหน้าแน่นอน (SPA) และยังคงสไตล์ CoreUI ด้วย class nav-link
import {
CSidebar,
CSidebarBrand,
CSidebarNav,
CNavItem,
CNavTitle,
} from "@coreui/react";
import { NavLink } from "react-router-dom";
export default function AppSidebar({ visible, setVisible, narrow }) {
return (
<CSidebar
visible={visible}
onVisibleChange={(val) => setVisible(val)}
narrow={narrow}
unfoldable
className="border-end bg-white"
>
<CSidebarBrand className="px-3 py-3 border-bottom">
<div className="d-flex align-items-center gap-2">
<div
className="d-flex align-items-center justify-content-center rounded"
style={{ width: 32, height: 32, background: "#eef2ff", color: "#4f46e5" }}
>
<i className="bi bi-grid-1x2-fill" />
</div>
<div className="nav-label">
<div className="fw-semibold">CoreUI Admin</div>
<div className="small text-body-secondary">Free Version</div>
</div>
</div>
</CSidebarBrand>
<CSidebarNav className="pt-2">
<CNavTitle className="text-uppercase small text-body-secondary px-3 mt-2 mb-1">
Overview
</CNavTitle>
<CNavItem>
<NavLink
to="/"
className={({ isActive }) =>
`nav-link d-flex align-items-center ${isActive ? "active" : ""}`
}
>
<i className="bi bi-speedometer2 me-2" />
<span className="nav-label">Dashboard</span>
</NavLink>
</CNavItem>
<CNavItem>
<NavLink
to="/users"
className={({ isActive }) =>
`nav-link d-flex align-items-center ${isActive ? "active" : ""}`
}
>
<i className="bi bi-people me-2" />
<span className="nav-label">Users</span>
</NavLink>
</CNavItem>
<CNavTitle className="text-uppercase small text-body-secondary px-3 mt-3 mb-1">
Management
</CNavTitle>
<CNavItem>
<a className="nav-link d-flex align-items-center" href="#">
<i className="bi bi-receipt me-2" />
<span className="nav-label">Reports</span>
<span className="ms-auto badge bg-secondary nav-label">Soon</span>
</a>
</CNavItem>
<CNavItem>
<a className="nav-link d-flex align-items-center" href="#">
<i className="bi bi-gear me-2" />
<span className="nav-label">Settings</span>
</a>
</CNavItem>
</CSidebarNav>
<div className="mt-auto px-3 py-3 border-top small text-body-secondary">
<span className="nav-label">ผู้สอน: ผู้ช่วยศาสตราจารย์ ปองพล นิลพฤกษ์</span>
</div>
</CSidebar>
);
}
Step 9: สร้าง Header (ชิดขวา) src/layout/AppHeader.jsx
แยกกลุ่มซ้าย/ขวา และใช้ ms-auto ดัน Search/Bell/Profile ไปชิดมุมขวา
import {
CHeader,
CContainer,
CHeaderBrand,
CHeaderNav,
CNavItem,
CNavLink,
CButton,
CDropdown,
CDropdownToggle,
CDropdownMenu,
CDropdownItem,
CForm,
CFormInput,
} from "@coreui/react";
export default function AppHeader({
sidebarVisible,
setSidebarVisible,
sidebarNarrow,
setSidebarNarrow,
}) {
return (
<CHeader position="sticky" className="border-bottom bg-white">
<CContainer fluid className="py-2">
<div className="d-flex align-items-center w-100">
{/* LEFT */}
<div className="d-flex align-items-center gap-2">
<CButton
color="light"
className="border shadow-sm"
onClick={() => setSidebarNarrow(!sidebarNarrow)}
aria-label="Collapse sidebar"
title="Collapse sidebar"
>
<i
className={`bi ${
sidebarNarrow ? "bi-layout-sidebar-inset" : "bi-layout-sidebar"
} fs-5`}
/>
</CButton>
<CButton
color="light"
className="border shadow-sm d-md-none"
onClick={() => setSidebarVisible(!sidebarVisible)}
aria-label="Show sidebar"
title="Show sidebar (mobile)"
>
<i className="bi bi-list fs-5" />
</CButton>
<CHeaderBrand className="mb-0 fw-semibold">
CoreUI Admin
</CHeaderBrand>
</div>
{/* RIGHT */}
<div className="d-flex align-items-center gap-2 ms-auto">
<div className="d-none d-md-block" style={{ width: 320 }}>
<CForm className="w-100">
<CFormInput type="search" placeholder="Search…" className="shadow-sm" />
</CForm>
</div>
<CHeaderNav className="align-items-center">
<CNavItem>
<CNavLink href="#" className="text-body">
<i className="bi bi-bell fs-5" />
</CNavLink>
</CNavItem>
</CHeaderNav>
<CDropdown alignment="end">
<CDropdownToggle color="light" className="border shadow-sm">
<i className="bi bi-person-circle fs-5" />
<span className="ms-2 d-none d-sm-inline">Admin</span>
</CDropdownToggle>
<CDropdownMenu>
<CDropdownItem href="#">Profile</CDropdownItem>
<CDropdownItem href="#">Settings</CDropdownItem>
<CDropdownItem href="#">Logout</CDropdownItem>
</CDropdownMenu>
</CDropdown>
</div>
</div>
</CContainer>
</CHeader>
);
}
Step 10: รวม Layout + Router src/layout/AppLayout.jsx + src/App.jsx
AppLayout คุม state ของ sidebar (visible/narrow) แล้วครอบทั้งแอปด้วย BrowserRouter
import { useState } from "react";
import { CContainer } from "@coreui/react";
import AppHeader from "./AppHeader";
import AppSidebar from "./AppSidebar";
import AppRoutes from "../routes/AppRoutes";
export default function AppLayout() {
const [sidebarVisible, setSidebarVisible] = useState(true);
const [sidebarNarrow, setSidebarNarrow] = useState(false);
return (
<div className="d-flex min-vh-100 bg-light">
<AppSidebar
visible={sidebarVisible}
setVisible={setSidebarVisible}
narrow={sidebarNarrow}
/>
<div className="flex-grow-1">
<AppHeader
sidebarVisible={sidebarVisible}
setSidebarVisible={setSidebarVisible}
sidebarNarrow={sidebarNarrow}
setSidebarNarrow={setSidebarNarrow}
/>
<main className="py-4">
<CContainer lg>
<div className="mb-3">
<h4 className="mb-1 fw-semibold">Dashboard</h4>
<div className="text-body-secondary">ภาพรวมข้อมูลสำคัญของระบบ</div>
</div>
<AppRoutes />
</CContainer>
</main>
</div>
</div>
);
}
import { BrowserRouter } from "react-router-dom";
import AppLayout from "./layout/AppLayout";
export default function App() {
return (
<BrowserRouter>
<AppLayout />
</BrowserRouter>
);
}
Step 11: Checklist + Run npm run dev
รันแล้วตรวจสอบว่าได้ครบ: Routing ทำงาน, Header ชิดขวา, Sidebar ย่อเหลือ icon, Dashboard มี KPI
npm run dev