Module 8: UI සහ Styling

හොඳ functionality එකක් මෙන්ම, පිරිසිදු සහ භාවිතයට පහසු UI (User Interface) එකක්ද සාර්ථක application එකකට අත්‍යවශ්‍යයි.

1. Bootstrap භාවිතයෙන් පිරිසිදු Dashboard UI එකක්

අපි මේ වන විටත් Bootstrap library එක අපේ project එකට CDN link එකක් හරහා එකතු කරගෙන, එහි මූලික classes (container, btn, form-control) භාවිතා කළා. දැන් අපි UI එක තවදුරටත් uluchch කිරීමට Bootstrap Cards සහ Grid System එක භාවිතා කරමු.

Module 7 හි අපි සෑදූ NotesList.tsx component එකේ notes list එක list-group එකක් ලෙස සෑදුවෙමු. අපි එය දැන් Bootstrap Cards බවට පත් කර, grid layout එකක පෙන්වමු. එමගින් එක් එක් සටහන වෙන් වෙන්ව, පිළිවෙලකට පෙනෙනු ඇත.

➡️ NotesList.tsx හි සටහන් Cards ලෙස පෙන්වීම

ඔබේ components/NotesList.tsx file එකේ return statement එක තුළ ඇති notes list එකට අදාළ කේතය පහත පරිදි වෙනස් කරන්න. මෙහිදී අපි list-group වෙනුවට row සහ col classes භාවිතා කර grid එකක් සාදා, එක් එක් note එක card එකක් ලෙස පෙන්වමු.

// Inside NotesList.tsx return statement

<div className="row g-3"> {/* g-3 adds gaps between cards */}
  {notes.length > 0 ? (
    notes.map(note => (
      <div key={note.id} className="col-md-6 col-lg-4">
        <div className="card h-100">
          <div className="card-body d-flex flex-column">
            <h5 className="card-title">{note.title}</h5>
            <p className="card-text flex-grow-1">{note.content}</p>
            <small className="text-muted">
              {new Date(note.created_at).toLocaleDateString()}
            </small>
            <div className="mt-3">
              <button className="btn btn-sm btn-secondary me-2" onClick={() => handleEdit(note)}>Edit</button>
              <button className="btn btn-sm btn-danger" onClick={() => handleDelete(note.id)}>Delete</button>
            </div>
          </div>
        </div>
      </div>
    ))
  ) : (
    <div className="col-12">
      <p>You haven't created any notes yet.</p>
    </div>
  )}
</div>

2. Responsive Layout (Mobile & Desktop)

Responsive design යනු වෙබ් අඩවියේ layout එක screen size එක අනුව (mobile, tablet, desktop) ස්වයංක්‍රීයව වෙනස් වීමයි. Bootstrap හි Grid System එක මේ සඳහා අපට විශාල සහයක් ලබා දේ.

ඉහත කේතයේ අප භාවිතා කළ classes වල තේරුම:

මේ ආකාරයට, කිසිදු අමතර CSS කේතයක් නොමැතිව අපේ app එක විවිධ devices වලට ගැලපෙන ලෙස සකස් වී ඇත.


3. Flash Messages / Toast Notifications

User කෙනෙක් යම් ක්‍රියාවක් (සටහනක් save කිරීම, delete කිරීම) කළ පසු, "Your note was saved successfully" වැනි පණිවිඩයක් පෙන්වීම ඉතා හොඳ user experience (UX) එකකි. මේවා **Flash Messages** හෝ **Toast Notifications** ලෙස හැඳින්වේ.

➡️ Toast.tsx Component එක සෑදීම

components folder එක තුළ Toast.tsx නමින් අලුත් file එකක් සාදා, toast එක පෙන්වීමට අවශ්‍ය UI සහ logic එක නිර්මාණය කරමු.

'use client';

import { useEffect } from 'react';

interface ToastProps {
  message: string;
  type: 'success' | 'error';
  onClose: () => void;
}

export default function Toast({ message, type, onClose }: ToastProps) {
  useEffect(() => {
    const timer = setTimeout(() => {
      onClose();
    }, 3000); // Close the toast after 3 seconds

    return () => clearTimeout(timer);
  }, [onClose]);

  const bgColor = type === 'success' ? 'bg-success' : 'bg-danger';

  return (
    <div 
      className={`position-fixed top-0 end-0 p-3`} 
      style={{ zIndex: 1050 }}
    >
      <div className={`toast show ${bgColor} text-white`} role="alert">
        <div className="d-flex">
            <div className="toast-body">
                {message}
            </div>
            <button type="button" className="btn-close btn-close-white me-2 m-auto" onClick={onClose}></button>
        </div>
      </div>
    </div>
  );
}
➡️ NotesList.tsx එකට Toast එක Integrate කිරීම

දැන් අපි NotesList.tsx file එක update කර, note එකක් delete හෝ update කළ විට toast එකක් පෙන්වීමට සලස්වමු.

පළමුව, Toast component එක import කර, toast එක handle කිරීමට state එකක් සාදාගන්න.

// Inside components/NotesList.tsx

import { useState, useEffect } from 'react';
import Toast from './Toast'; // Import the Toast component

// ... inside the NotesList component function
const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' } | null>(null);

// Update handleDelete function
const handleDelete = async (id: number) => {
  if (confirm('Are you sure you want to delete this note?')) {
    await fetch(`/api/notes/${id}`, { method: 'DELETE' });
    setNotes(notes.filter(note => note.id !== id));
    setToast({ message: 'Note deleted successfully', type: 'success' }); // Show toast
  }
};

// Update handleUpdate function
const handleUpdate = async (e: React.FormEvent) => {
  // ... (existing code)
  if (res.ok) {
    // ... (existing code)
    setEditingNote(null);
    setToast({ message: 'Note updated successfully', type: 'success' }); // Show toast
  } else {
    setToast({ message: 'Failed to update note', type: 'error' }); // Show error toast
  }
};

// Inside the return statement, add the Toast component
return (
  <>
    {toast && <Toast message={toast.message} type={toast.type} onClose={() => setToast(null)} />}
    {/* ... rest of the component (notes grid and modal) ... */}
  </>
);

දැන් ඔබ note එකක් update හෝ delete කළ විට, තත්පර 3ක් සඳහා තිරයේ දකුණුපස ඉහළ කෙළවරේ සාර්ථක/අසාර්ථක බවට පණිවිඩයක් දිස්වී, ඉබේම மறைந்து යනු ඇත.