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

Day 3 / Hour 2

Tailwind Components and Responsive UI

Status badges, issue list, form layout, responsive behavior, and empty states.

60 minutes
The main UI is readable and responsive.

Tailwind Components and Responsive UI

Day 3 - ชั่วโมงที่ 2: ปรับ Component และตารางด้วย Tailwind

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

หลังจบชั่วโมงนี้ นักศึกษาจะสามารถ:

  1. ใช้ Tailwind กำหนดหน้าตาของ Component ตาม Props ได้
  2. ปรับ StatusBadge ให้แสดงสีตามสถานะ
  3. ปรับ IssueList ให้อ่านง่ายขึ้น
  4. ทำให้ตารางเลื่อนแนวนอนได้เมื่อพื้นที่ไม่พอ
  5. แสดง Empty State เมื่อไม่มีรายการ
  6. ลบ Custom CSS เดิมที่ไม่ใช้งานแล้วได้อย่างถูกต้อง

ไฟล์ที่ใช้

components/StatusBadge.tsx
components/IssueList.tsx
app/globals.css
app/issues/page.tsx

Slide 1: ต่อจากหน้าตาเว็บใน Hour 1

เมนูนำทางและฟอร์มใช้ Tailwind แล้ว ส่วนป้ายสถานะ (StatusBadge) และตารางใน IssueList ยังใช้ CSS เดิมภายใน @layer base

ชั่วโมงนี้จะย้ายสอง Component ที่เหลือ แล้วเพิ่มข้อความเมื่อไม่มีรายการ

  • ใช้ issues และ Props เดิมจาก Day 2
  • เก็บ Import, Type และ Link รายละเอียดไว้
  • CSS ของ .issue-list ถูกลบใน Hour 1 แล้ว เพราะ <main> ใช้ gap-6 แทนระยะด้านบน
  • main ยังเป็นรูปแบบพื้นฐานของหน้าที่ยังไม่ได้ย้าย

ลำดับงาน: สีสถานะ → ตารางบนจอเล็ก → ข้อความเมื่อไม่มีข้อมูล


Slide 2: ให้ฟังก์ชันเลือกคลาสสีตามสถานะ

function getStatusClass(status: IssueStatus): string {  if (status === "OPEN") {    return "bg-red-50 text-red-700 ring-red-200";  }  if (status === "IN_PROGRESS") {    return "bg-amber-50 text-amber-700 ring-amber-200";  }  return "bg-emerald-50 text-emerald-700 ring-emerald-200";}

getStatusClass เป็นฟังก์ชันช่วยเลือกสีแดง เหลือง และเขียวตามสถานะ โดย ring-* กำหนดสีเส้นรอบป้ายที่จะเพิ่มในขั้นถัดไป


Slide 3: ใช้คลาส Tailwind กับป้ายสถานะ

<span  className={`inline-flex rounded-full px-2.5 py-1    text-xs font-bold ring-1 ${getStatusClass(status)}`}>  {status}</span>

rounded-full ทำให้มุมโค้ง ส่วน ring-1 แสดงเส้นรอบป้าย และ getStatusClass เลือกสีตาม status

Badge ควรยังแสดงข้อความ OPEN, IN_PROGRESS และ DONE พร้อมสีที่ต่างกัน


Slide 4: ลบ CSS เดิมของ Badge

ตัวอย่างย่อระบุ Block ที่ต้องลบทั้งก้อน ไม่คัดลอก Comment ไปแทนโค้ดจริง

.status { /* Property เดิม */ }.status-open { /* Property เดิม */ }.status-progress { /* Property เดิม */ }.status-done { /* Property เดิม */ }

หลังลบ สีและรูปทรงของ Badge ควรเหมือนก่อนลบ เพราะมาจาก Utility แล้ว


Slide 5: ปรับกรอบของ IssueList

<section  aria-labelledby="issue-list-title"  className="min-w-0 rounded-lg border border-slate-200 bg-white p-6">

คงหัวข้อและตารางทั้งหมดภายใน Section รวมถึง </section> เดิมไว้

min-w-0 ช่วยให้กล่องย่อได้เมื่ออยู่ใน Grid ส่วนพื้นขาว เส้นขอบ และระยะภายในมาจาก Utility


Slide 6: จัดหัวข้อและจำนวนรายการ

<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">  <h2 id="issue-list-title" className="text-xl font-bold text-slate-950">    รายการปัญหาล่าสุด  </h2>  <p className="text-sm font-medium text-slate-600">    ทั้งหมด {issues.length} รายการ  </p></div>

issues.length คือจำนวนรายการ บนจอเล็กหัวข้อและจำนวนเรียงลงมา ตั้งแต่ sm (640px) จะอยู่แถวเดียวกัน

ตารางเดิมต้องยังอยู่ถัดจาก </div> นี้ภายใน Section


Slide 7: ให้ตารางเลื่อนภายในกล่อง

<div className="mt-4 overflow-x-auto">  <table className="w-full min-w-[720px] border-collapse text-sm">

แสดงเฉพาะ Tag เปิดที่แก้ ให้เก็บ <thead>, <tbody> และ Tag ปิดเดิมไว้ครบ

  • overflow-x-auto ให้กล่องเลื่อนแนวนอนได้เมื่อพื้นที่ไม่พอ
  • min-w-[720px] กำหนดความกว้างขั้นต่ำของตารางเป็น 720px
  • w-full ให้ตารางกว้างเต็มกล่องเมื่อมีพื้นที่พอ

บนจอเล็กควรเลื่อนเฉพาะตาราง โดยหน้าเว็บไม่ล้นด้านข้าง


Slide 8: ปรับหัวตาราง

<thead>  <tr className="border-b border-slate-200 bg-slate-50 text-left text-xs text-slate-500">    <th className="px-3 py-3 font-semibold">รหัส</th>    <th className="px-3 py-3 font-semibold">หัวข้อ</th>    <th className="px-3 py-3 font-semibold">ผู้แจ้ง</th>    <th className="px-3 py-3 font-semibold">สถานะ</th>    <th className="px-3 py-3 font-semibold">รายละเอียด</th>  </tr></thead>

หัวตารางควรมีพื้นสีอ่อน ข้อความชิดซ้าย และระยะภายในแต่ละช่องเท่ากัน

ยังเก็บ CSS ของ Table ไว้ก่อน จนกว่าจะใส่ Utility ครบทั้งหัวตารางและแถวข้อมูล


Slide 9: เพิ่ม Style ให้แถวและช่องข้อมูล

<tr key={issue.id} className="border-b border-slate-100 last:border-0">  <td className="px-3 py-4 font-medium text-slate-700">  <td className="px-3 py-4 text-slate-950">  <td className="px-3 py-4 text-slate-600">  <td className="px-3 py-4">  <td className="px-3 py-4">

แสดงเฉพาะ Tag เปิดตามลำดับ: แถว → รหัส → หัวข้อ → ผู้แจ้ง → สถานะ → รายละเอียด

เก็บ .map(), ข้อมูลแต่ละช่อง, StatusBadge, Link และ Tag ปิดไว้ครบ ห้ามวางหกบรรทัดนี้ติดกันแทนทั้งแถว

last:border-0 เอาเส้นคั่นของแถวสุดท้ายออก


Slide 10: ปรับ Link รายละเอียด

<Link  href={`/issues/${issue.id}`}  className="font-semibold text-teal-700 hover:text-teal-900">  ดูรายละเอียด</Link>

คง <td> ที่ครอบ Link และ href เดิมไว้ แต่ละแถวยังเปิดรายละเอียดของ issue.id นั้น

ข้อความควรเป็นสีเขียวเข้มและเปลี่ยนสีเมื่อชี้เมาส์


Slide 11: ลบ CSS เดิมของรายการและตาราง

ตัวอย่างย่อระบุ Block ที่ลบทั้งก้อน ไม่คัดลอก Comment ไปแทนโค้ดจริง

.table-wrapper { /* Property เดิม */ }table { /* Property เดิม */ }th, td { /* Property เดิมของกฎร่วม */ }th { /* Property เดิม */ }section { /* Property เดิม */ }

.issue-list ถูกลบไปแล้วใน Hour 1 ส่วน IssueForm และ IssueList มี Utility ที่ Section แล้ว จึงลบ section กลางได้

เก็บ Import, วงเล็บของ @layer base, *, body และ main ไว้; หลังลบ Form และตารางควรยังมี Style ครบ


Slide 12: เพิ่มข้อความเมื่อไม่มีรายการ

Empty State คือข้อความที่ช่วยให้รู้ว่าไม่มีข้อมูล แทนการแสดงตารางว่าง

if (issues.length === 0) {  return (    <section className="rounded-lg border border-dashed border-slate-300 bg-white p-8 text-center">      <h2 className="text-lg font-bold text-slate-950">ยังไม่มีรายการปัญหา</h2>      <p className="mt-2 text-sm text-slate-600">        เมื่อมีการแจ้งปัญหา รายการจะแสดงที่นี่      </p>    </section>  );}

เมื่อ Array ว่าง Function จะคืนข้อความนี้ทันที ส่วน return หลักที่แสดงตารางยังเก็บไว้ด้านล่าง


Slide 13: จัดวางเนื้อหาของหน้า /issues

<main className="mx-auto grid grid-cols-1 max-w-5xl gap-6 px-6 py-8">  <h1 className="text-2xl font-bold text-slate-950">รายการปัญหา</h1>  <IssueList issues={issues} /></main>

เก็บ Import ของ IssueList, issues และ Function IssuesPage เดิมไว้

หน้า /issues ควรมีความกว้างและระยะขอบเหมือน Home ส่วน grid-cols-1 ช่วยให้ตารางย่ออยู่ภายในพื้นที่ของหน้า

ยังเก็บ CSS กลางของ main ให้ Route อื่นที่ยังไม่ได้ย้าย


Slide 14: ลองส่งรายการว่าง

<IssueList issues={[]} />

หน้า /issues ควรแสดง ยังไม่มีรายการปัญหา และไม่แสดงตาราง

หลังตรวจแล้ว เปลี่ยนบรรทัดเดิมกลับเป็น:

<IssueList issues={issues} />

ตาราง Mock Data ควรกลับมา โดยไม่ต้องแก้ data/issue.ts


Slide 15: ลองใช้งานทั้งสองหน้า

ที่ความกว้าง 375px และ 1024px ควรได้ผลดังนี้:

  • หน้าเว็บไม่ล้นด้านข้าง; บนจอเล็กเลื่อนเฉพาะตารางได้
  • หัวข้อและจำนวนรายการเรียงลงมาบนจอเล็ก และอยู่แถวเดียวกันบนจอกว้าง
  • Badge แสดงครบทั้งสามสถานะพร้อมสีที่ต่างกัน
  • Link รายละเอียดเปิดรายการตรงกับแถวที่เลือก
  • Console และ Terminal ไม่มี Error

ถ้าทั้งหน้าล้น ให้ตรวจ min-w-0 ที่ Section, grid-cols-1 ที่ Main และ overflow-x-auto ที่กล่องครอบ Table


Slide 16: พร้อมเพิ่มการทำงานให้ Form

ตอนนี้ IssueList รับ issues ชุดเดิม แล้วแสดงตารางหรือข้อความเมื่อไม่มีข้อมูลได้

  • Home และ /issues ใช้ Component ชุดเดียวกัน
  • Form, Badge และ Table ใช้ Tailwind แล้ว
  • CSS พื้นฐานของ main ยังรองรับ Route อื่น

ฟอร์มยังไม่เพิ่มรายการ เพราะเรายังไม่ได้เชื่อมการกดส่งกับข้อมูล

Hour 3 จะเพิ่มการเก็บรายการที่เปลี่ยนได้และการตรวจข้อมูล เพื่อให้ข้อมูลจากฟอร์มปรากฏในตาราง