FileEditViewTerminal
IT Issue Bootcamp - Visual Studio Code
day-2-hour-2.mdx

Day 2 / Hour 2

Convert Static to Next.js

Move HTML/CSS prototype into TSX and Next.js app structure.

60 minutes
The static prototype renders inside Next.js.

Convert Static to Next.js

Day 2 - ชั่วโมงที่ 2: ย้าย Static Prototype เข้า Next.js

เป้าหมายของชั่วโมงนี้

หลังจบชั่วโมงที่สองของ Day 2 ผู้เรียนควรสามารถ:

  1. เปลี่ยนโครงสร้าง HTML จาก Day 1 ให้เป็น TSX ได้
  2. ใช้ className, htmlFor และ TSX Comment ได้ถูกต้อง
  3. ย้าย Header, Form, Issue List และ Footer เข้า app/page.tsx ได้
  4. ย้าย CSS เดิมเข้า app/globals.css ได้
  5. ตรวจว่า Static Prototype แสดงผลใน Next.js ได้เหมือนเดิม

ไฟล์ที่ใช้ในชั่วโมงนี้

app/layout.tsx
app/page.tsx
app/globals.css

เปิด Development Server ด้วย npm run dev ระหว่างทำงาน


โครงสร้างเวลา 60 นาที

เวลาหัวข้อรูปแบบ
0-10 นาทีวางแผนการย้าย HTML/CSSอธิบาย
10-20 นาทีรู้จักความต่างระหว่าง HTML กับ TSXทำทีละขั้นตอน
20-40 นาทีย้ายโครงหน้า, Form และ Issue Listทำทีละขั้นตอน
40-50 นาทีย้าย CSS เข้า globals.cssทำทีละขั้นตอน
50-60 นาทีตรวจผลลัพธ์ใน Browserตรวจความพร้อม

Slide 1: ย้ายหน้าเดิมโดยยังไม่เพิ่ม Feature

Hour ที่แล้วเราสร้าง Next.js Project และเปิดหน้าแรกได้แล้ว

Hour นี้เราจะนำ Static Prototype จาก Day 1 เข้ามาอยู่ใน Project ใหม่:

  • Header
  • Form แจ้งปัญหา
  • Issue List และ Status Badge
  • Footer
  • Responsive CSS

ข้อมูลใน Table ยังเป็น Mock Data และ Form ยังไม่บันทึกข้อมูล เป้าหมายตอนนี้คือย้ายหน้าตาเดิมให้ทำงานใน Next.js ก่อน


Slide 2: ไฟล์เดิมจะย้ายไปที่ไหน

จาก Day 1ไปยัง Next.jsสิ่งที่ย้าย
<html> และ <body>app/layout.tsxภาษาและกรอบหลัก
เนื้อหาภายใน <body>app/page.tsxHeader, Main และ Footer
style.cssapp/globals.cssCSS เดิมทั้งหมด

เราไม่ย้าย <!doctype html>, <head> หรือ <link rel="stylesheet"> เข้า page.tsx เพราะ Next.js จัดการ Document และการโหลด CSS ผ่าน layout.tsx


Slide 3: TSX คืออะไร

TSX คือ Syntax ที่ทำให้เราเขียนโครงสร้าง UI คล้าย HTML อยู่ภายใน Code TypeScript ได้

ไฟล์ที่มีทั้ง TypeScript และ UI แบบนี้จึงใช้นามสกุล .tsx เช่น app/page.tsx

const title = "ระบบแจ้งปัญหา IT";
 
export default function Home() {
  return <h1>{title}</h1>;
}
  • <h1>...</h1> คือโครงสร้าง UI ที่มีหน้าตาคล้าย HTML
  • title คือค่าที่สร้างด้วย TypeScript
  • {title} นำค่านั้นมาแสดงภายใน UI

TSX ยังไม่ใช่ HTML ที่ Browser ใช้โดยตรง Next.js และ React จะนำ Code นี้ไปสร้าง Element บนหน้าเว็บให้เรา


Slide 4: กฎสำคัญเมื่อเปลี่ยน HTML เป็น TSX

HTMLTSX
class="form-group"className="form-group"
<label for="title"><label htmlFor="title">
<input required><input required />
rows="5"rows={5}
<!-- เพิ่ม Form -->{/* เพิ่ม Form */}

ชื่ออย่าง id, name, type, required และ aria-labelledby ยังใช้เหมือน HTML

ถ้าพบ Error หลัง Copy Code ให้เริ่มตรวจจากห้าจุดในตารางนี้ก่อน


Slide 5: เปลี่ยนภาษาหลักของหน้าเป็นภาษาไทย

แก้บางส่วน: app/layout.tsx

ค้นหา <html lang="en"> แล้วเปลี่ยนเฉพาะค่า lang:

<html lang="th">

เก็บ Import, Font, Class และ {children} ที่ Next.js สร้างมาไว้เหมือนเดิม

lang="th" ช่วยให้ Browser และ Screen Reader ทราบว่าภาษาหลักของหน้าเป็นภาษาไทย


Slide 6: สร้างโครงหลักของ app/page.tsx

แทน Code ทั้งไฟล์: app/page.tsx

export default function Home() {
  return (
    <>
      <header>
        <h1>ระบบแจ้งปัญหา IT</h1>
        <p>แจ้งและติดตามปัญหาการใช้งานระบบภายใน</p>
      </header>
 
      <main>
        <section>
          <h2>แจ้งปัญหาใหม่</h2>
          {/* เพิ่ม Form ตรงนี้ */}
        </section>
 
        <section aria-labelledby="issue-list-title">
          <h2 id="issue-list-title">รายการปัญหาล่าสุด</h2>
          <p>ตัวอย่างรายการปัญหาที่ถูกแจ้งเข้ามาในระบบ</p>
          <div className="table-wrapper">{/* เพิ่ม Table ตรงนี้ */}</div>
        </section>
      </main>
 
      <footer>
        <p>ฝ่ายเทคโนโลยีสารสนเทศ</p>
      </footer>
    </>
  );
}

<>...</> เรียกว่า Fragment ใช้ครอบ Element หลายตัวโดยไม่สร้าง Tag เพิ่มบนหน้าเว็บ


Slide 7: ย้ายข้อมูลผู้แจ้งเข้า Form

แทน Comment {/* เพิ่ม Form ตรงนี้ */} ใน app/page.tsx

<form>
  <fieldset>
    <legend>ข้อมูลปัญหา</legend>
 
    <div className="form-row">
      <div className="form-group">
        <label htmlFor="reporterName">ชื่อผู้แจ้ง</label>
        <input id="reporterName" name="reporterName" type="text" required />
      </div>
 
      <div className="form-group">
        <label htmlFor="reporterEmail">อีเมลผู้แจ้ง</label>
        <input id="reporterEmail" name="reporterEmail" type="email" required />
      </div>
    </div>
 
    {/* เพิ่มข้อมูลปัญหาตรงนี้ */}
  </fieldset>
</form>

Form ยังอยู่ใน Component Home เหมือนเดิม เพียงเปลี่ยน class เป็น className และ for เป็น htmlFor


Slide 8: เติมข้อมูลปัญหาและปุ่ม Submit

แทน Comment {/* เพิ่มข้อมูลปัญหาตรงนี้ */} ใน Form

<div className="form-group">
  <label htmlFor="title">หัวข้อปัญหา</label>
  <input id="title" name="title" type="text" required />
</div>
 
<div className="form-group">
  <label htmlFor="description">รายละเอียดปัญหา</label>
  <textarea id="description" name="description" rows={5} required />
</div>
 
<button type="submit">ส่งข้อมูล</button>

ชื่อ Field ยังคงเป็น reporterName, reporterEmail, title และ description เพื่อให้ตรงกับข้อมูลที่จะสร้างในบทเรียนถัดไป


Slide 9: สร้างโครง Table ใน Issue List

แทน Comment {/* เพิ่ม Table ตรงนี้ */} ภายใน table-wrapper

<table>
  <thead>
    <tr>
      <th>รหัส</th>
      <th>หัวข้อ</th>
      <th>ผู้แจ้ง</th>
      <th>สถานะ</th>
    </tr>
  </thead>
  <tbody>{/* เพิ่ม Rows ตรงนี้ */}</tbody>
</table>

โครงสร้าง Table ยังเหมือน HTML เดิม ส่วนที่เปลี่ยนเป็น TSX ชัดที่สุดคือ Comment ซึ่งต้องเขียนอยู่ภายใน {/* ... */}


Slide 10: เพิ่ม Mock Rows กลับเข้า Table

แทน Comment {/* เพิ่ม Rows ตรงนี้ */} ภายใน <tbody>

<tr>
  <td>#001</td>
  <td>Login เข้าระบบไม่ได้</td>
  <td>Anan</td>
  <td><span className="status status-open">OPEN</span></td>
</tr>
<tr>
  <td>#002</td>
  <td>ส่งแบบฟอร์มไม่ได้</td>
  <td>Mali</td>
  <td><span className="status status-progress">IN_PROGRESS</span></td>
</tr>
<tr>
  <td>#003</td>
  <td>ขอสิทธิ์เข้า Dashboard</td>
  <td>Kanda</td>
  <td><span className="status status-done">DONE</span></td>
</tr>

แต่ละ Badge ใช้ Class พื้นฐาน status ร่วมกัน และใช้ Class ตัวที่สองกำหนดสีเฉพาะสถานะ


Slide 11: ย้าย CSS เดิมเข้า globals.css

เปิด app/globals.css แล้วทำตามลำดับ:

  1. ลบ CSS เริ่มต้นทั้งหมด รวมถึงบรรทัด @import "tailwindcss";
  2. Copy เนื้อหาทั้งหมดจาก style.css ของ Day 1
  3. Paste ลงใน app/globals.css

โครงไฟล์หลังย้าย:

/* CSS ทั้งหมดจาก style.css ของ Day 1 */

ไม่ต้อง Copy <link rel="stylesheet"> เพราะบรรทัดนั้นเป็น HTML และ globals.css ถูก Import จาก layout.tsx อยู่แล้ว


Slide 12: ทำไม Hour นี้ยังไม่ใช้ Tailwind

Project มี Tailwind Package จาก create-next-app อยู่แล้ว แต่บรรทัดนี้เป็นตัวเปิดใช้งาน Tailwind และ Preflight:

@import "tailwindcss";

Preflight คือ Style พื้นฐานที่ Tailwind ใส่ให้กับ Tag ต่าง ๆ ก่อนที่เราจะเริ่มใช้ Utility Class

ใน Hour นี้เรายังใช้ Custom CSS จาก Day 1 ซึ่งมี Selector ของ Tag เช่น main, section, button และ table อยู่แล้ว หากเปิด Tailwind พร้อมกัน ผลลัพธ์อาจเปลี่ยนจาก Cascade ของ CSS สองชุดและทำให้ Debug ยากขึ้น

การลบ @import ไม่ได้ถอน Tailwind ออกจาก Project เพียงยังไม่โหลด Tailwind ในหน้านี้เท่านั้น Day 3 เราจะใส่บรรทัดนี้กลับมา แล้วค่อย ๆ เปลี่ยน Custom CSS เป็น Tailwind Utility Class


Slide 13: Media Query ของ Custom CSS ยังทำงาน

Media Query ที่ Copy มาจาก Day 1 ยังคงอยู่ใน app/globals.css:

@media (min-width: 720px) {
  .form-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
  }
}

เงื่อนไขนี้จะทำงานเมื่อ Viewport กว้างตั้งแต่ 720px ขึ้นไป เท่านั้น:

ความกว้าง Viewportผลลัพธ์ของ Form
ต่ำกว่า 720pxชื่อและอีเมลเรียงลงมา 1 คอลัมน์
ตั้งแต่ 720px ขึ้นไปชื่อและอีเมลเรียงเป็น 2 คอลัมน์

ตัวอย่างเช่น Viewport กว้าง 631px ยังไม่ถึง 720px Form จึงแสดง 1 คอลัมน์ ซึ่งเป็นผลลัพธ์ที่ถูกต้อง


Slide 14: ตรวจหน้าใน Browser

เปิด URL ของ Development Server แล้วตรวจ:

  • Header, Form, Issue List และ Footer แสดงครบ
  • Form มี Field ทั้ง 4 ช่อง
  • คลิก Label แล้ว Focus ไปยัง Field ที่ตรงกัน
  • Table แสดง Mock Data 3 รายการ
  • Status Badge มีทั้งสีและข้อความ
  • เมื่อ Viewport แคบกว่า 720px ช่องชื่อและอีเมลเรียงลงมา 1 คอลัมน์
  • เมื่อ Viewport กว้างตั้งแต่ 720px ช่องชื่อและอีเมลเปลี่ยนเป็น 2 คอลัมน์
  • เมื่อพื้นที่ของ Table แคบกว่า 680px สามารถเลื่อน Table แนวนอนได้
  • Scrollbar แนวนอนอยู่ใต้ Table เท่านั้น และไม่มี Scrollbar ใต้ทั้งหน้า
  • Terminal และ Browser ไม่มี Error

เมื่อกด Submit ข้อมูลยังไม่ถูกเพิ่มใน Table เพราะเรายังไม่ได้เขียน Logic หรือเชื่อม Database


Slide 15: ตอนนี้ Static Prototype อยู่ใน Next.js แล้ว

สิ่งที่เปลี่ยนใน Hour นี้:

HTML structure  → TSX ใน app/page.tsx
Document lang   → app/layout.tsx
CSS เดิม        → app/globals.css

สิ่งที่ยังเหมือนเดิม:

  • Mock Data ยังเขียนอยู่ใน Table
  • Form ยังไม่บันทึกข้อมูล
  • UI ยังไม่ได้แยกเป็น Component
  • Tailwind Package ยังอยู่ใน Project แต่ยังไม่ถูกเปิดใช้งานใน globals.css

Hour ถัดไปเราจะสร้าง TypeScript Data Model และเปลี่ยน Static Rows ให้สร้างจากข้อมูลด้วย .map()