Index: frontend/Dockerfile
===================================================================
--- frontend/Dockerfile	(revision 545738f221bcda7888ec3b35279f55d0f80c4faa)
+++ frontend/Dockerfile	(revision 545738f221bcda7888ec3b35279f55d0f80c4faa)
@@ -0,0 +1,11 @@
+FROM node:22-alpine AS build
+WORKDIR /app
+COPY package*.json ./
+RUN npm ci --legacy-peer-deps
+COPY . .
+RUN VITE_API_BASE_URL=/api npm run build
+
+FROM nginx:alpine
+COPY --from=build /app/dist /usr/share/nginx/html
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+EXPOSE 80
Index: frontend/nginx.conf
===================================================================
--- frontend/nginx.conf	(revision 545738f221bcda7888ec3b35279f55d0f80c4faa)
+++ frontend/nginx.conf	(revision 545738f221bcda7888ec3b35279f55d0f80c4faa)
@@ -0,0 +1,31 @@
+server {
+    listen 80;
+
+    root /usr/share/nginx/html;
+    index index.html;
+
+    # Proxy API calls to the backend container
+    location /api/ {
+        proxy_pass http://backend:8080/api/;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+    }
+
+    # Yahoo Finance proxy (replaces the Vite dev proxy)
+    location /yahoo/ {
+        proxy_pass https://query1.finance.yahoo.com/;
+        proxy_ssl_server_name on;
+        proxy_set_header Host query1.finance.yahoo.com;
+    }
+
+    location /yahoo2/ {
+        proxy_pass https://query2.finance.yahoo.com/;
+        proxy_ssl_server_name on;
+        proxy_set_header Host query2.finance.yahoo.com;
+    }
+
+    # SPA fallback
+    location / {
+        try_files $uri $uri/ /index.html;
+    }
+}
Index: frontend/src/api/discipline.js
===================================================================
--- frontend/src/api/discipline.js	(revision 447c39f7111f1415e8f44660911d00bb61568780)
+++ frontend/src/api/discipline.js	(revision 545738f221bcda7888ec3b35279f55d0f80c4faa)
@@ -81,4 +81,12 @@
 }
 
+export async function updateCustomCategoryTaskStatus(customTrackingId, taskId, status) {
+  const res = await api.patch(
+    `/discipline/custom-tracking-categories/${customTrackingId}/tasks/${taskId}/status`,
+    { status }
+  );
+  return res?.data;
+}
+
 export async function deleteCustomCategoryTask(customTrackingId, taskId) {
   return api.delete(
Index: frontend/src/pages/Dashboard/pages/CustomCategory/CustomCategoryKanban.jsx
===================================================================
--- frontend/src/pages/Dashboard/pages/CustomCategory/CustomCategoryKanban.jsx	(revision 447c39f7111f1415e8f44660911d00bb61568780)
+++ frontend/src/pages/Dashboard/pages/CustomCategory/CustomCategoryKanban.jsx	(revision 545738f221bcda7888ec3b35279f55d0f80c4faa)
@@ -8,5 +8,5 @@
   getCustomTrackingCategories,
   updateCustomCategoryTask,
-  updateCustomCategoryTaskFinished,
+  updateCustomCategoryTaskStatus,
 } from "../../../../api/discipline.js";
 
@@ -23,7 +23,10 @@
 
   const lanes = useMemo(() => {
-    const notDone = (tasks ?? []).filter((t) => !t.isFinished);
-    const done = (tasks ?? []).filter((t) => t.isFinished);
-    return { notDone, done };
+    const all = tasks ?? [];
+    return {
+      notStarted: all.filter((t) => t.status === "NOT_STARTED"),
+      inProgress: all.filter((t) => t.status === "IN_PROGRESS"),
+      finished: all.filter((t) => t.status === "FINISHED"),
+    };
   }, [tasks]);
 
@@ -36,9 +39,7 @@
         getCustomCategoryTasks(customTrackingId),
       ]);
-
       const cats = catsRes?.items ?? [];
       const found = cats.find((c) => Number(c.customTrackingId) === customTrackingId);
       setCategoryName(found?.name ?? "Custom Category");
-
       setTasks(tasksRes?.tasks ?? []);
     } catch (e) {
@@ -59,11 +60,11 @@
   }, [customTrackingId]);
 
-  async function handleAddTask(name) {
-    const created = await createCustomCategoryTask(customTrackingId, { name });
+  async function handleAddTask(payload) {
+    const created = await createCustomCategoryTask(customTrackingId, payload);
     setTasks((prev) => [created, ...(prev ?? [])]);
   }
 
-  async function handleEditTask(taskId, name) {
-    const updated = await updateCustomCategoryTask(customTrackingId, taskId, { name });
+  async function handleEditTask(taskId, payload) {
+    const updated = await updateCustomCategoryTask(customTrackingId, taskId, payload);
     setTasks((prev) => (prev ?? []).map((t) => (t.taskId === taskId ? updated : t)));
   }
@@ -74,23 +75,13 @@
   }
 
-  async function handleMoveTask(taskId, toFinished) {
-    // optimistic update
+  async function handleMoveTask(taskId, toStatus) {
     setTasks((prev) =>
-      (prev ?? []).map((t) =>
-        t.taskId === taskId ? { ...t, isFinished: toFinished } : t
-      )
+      (prev ?? []).map((t) => (t.taskId === taskId ? { ...t, status: toStatus } : t))
     );
-
     try {
-      const saved = await updateCustomCategoryTaskFinished(
-        customTrackingId,
-        taskId,
-        toFinished
-      );
+      const saved = await updateCustomCategoryTaskStatus(customTrackingId, taskId, toStatus);
       setTasks((prev) => (prev ?? []).map((t) => (t.taskId === taskId ? saved : t)));
-    } catch (e) {
-      // revert by refetching (simple + correct)
+    } catch {
       await refresh();
-      throw e;
     }
   }
@@ -102,5 +93,5 @@
           <h1 className="text-2xl font-bold">{categoryName}</h1>
           <p className="text-sm opacity-80">
-            Drag tickets between columns to mark them done / not done.
+            Drag tickets between columns or use the edit form to update status.
           </p>
         </div>
@@ -115,6 +106,7 @@
       <CustomCategoryKanbanBoard
         loading={loading}
-        notDone={lanes.notDone}
-        done={lanes.done}
+        notStarted={lanes.notStarted}
+        inProgress={lanes.inProgress}
+        finished={lanes.finished}
         onAddTask={handleAddTask}
         onEditTask={handleEditTask}
@@ -125,4 +117,2 @@
   );
 }
-
-
Index: frontend/src/pages/Dashboard/pages/CustomCategory/components/CustomCategoryKanbanBoard.jsx
===================================================================
--- frontend/src/pages/Dashboard/pages/CustomCategory/components/CustomCategoryKanbanBoard.jsx	(revision 447c39f7111f1415e8f44660911d00bb61568780)
+++ frontend/src/pages/Dashboard/pages/CustomCategory/components/CustomCategoryKanbanBoard.jsx	(revision 545738f221bcda7888ec3b35279f55d0f80c4faa)
@@ -1,238 +1,194 @@
 import React, { useMemo, useState } from "react";
-
-import {
-  DndContext,
-  PointerSensor,
-  closestCenter,
-  useSensor,
-  useSensors,
-} from "@dnd-kit/core";
-import {
-  SortableContext,
-  arrayMove,
-  rectSortingStrategy,
-} from "@dnd-kit/sortable";
+import { DndContext, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
+import { SortableContext, rectSortingStrategy } from "@dnd-kit/sortable";
+import { MdAdd, MdSort } from "react-icons/md";
 
 import KanbanColumn from "./KanbanColumn";
 import TaskTicketCard from "./TaskTicketCard";
+import TaskFormModal from "./TaskFormModal";
 
-function laneIdForTask(task) {
-  return task?.isFinished ? "done" : "notDone";
+const LANES = [
+  { id: "NOT_STARTED", droppableId: "lane:NOT_STARTED", title: "Not Started" },
+  { id: "IN_PROGRESS", droppableId: "lane:IN_PROGRESS", title: "In Progress" },
+  { id: "FINISHED",    droppableId: "lane:FINISHED",    title: "Finished" },
+];
+
+const PRIORITY_ORDER = { HIGH: 0, MEDIUM: 1, LOW: 2, null: 3, undefined: 3 };
+
+function sortTasks(tasks, sortBy) {
+  if (!tasks || tasks.length === 0) return tasks;
+  const arr = [...tasks];
+  if (sortBy === "dueDate") {
+    arr.sort((a, b) => {
+      if (!a.dueDate && !b.dueDate) return 0;
+      if (!a.dueDate) return 1;
+      if (!b.dueDate) return -1;
+      return new Date(a.dueDate) - new Date(b.dueDate);
+    });
+  } else if (sortBy === "priority") {
+    arr.sort((a, b) => (PRIORITY_ORDER[a.priority] ?? 3) - (PRIORITY_ORDER[b.priority] ?? 3));
+  }
+  return arr;
+}
+
+function statusForDropId(dropId) {
+  return dropId.replace("lane:", "");
 }
 
 export default function CustomCategoryKanbanBoard({
-  loading,
-  notDone,
-  done,
-  onAddTask,
-  onEditTask,
-  onDeleteTask,
-  onMoveTask,
+  loading, notStarted, inProgress, finished,
+  onAddTask, onEditTask, onDeleteTask, onMoveTask,
 }) {
-  const [newName, setNewName] = useState("");
-  const [submitting, setSubmitting] = useState(false);
+  const [modalOpen, setModalOpen] = useState(false);
+  const [editingTask, setEditingTask] = useState(null);
+  const [modalLoading, setModalLoading] = useState(false);
+  const [sortBy, setSortBy] = useState("default");
 
   const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } }));
 
-  // Keep a local order per lane for nicer UX, but still driven by server state.
-  const [notDoneOrder, setNotDoneOrder] = useState([]);
-  const [doneOrder, setDoneOrder] = useState([]);
+  const tasksByLane = useMemo(() => ({
+    NOT_STARTED: sortTasks(notStarted ?? [], sortBy),
+    IN_PROGRESS: sortTasks(inProgress ?? [], sortBy),
+    FINISHED:    sortTasks(finished ?? [], sortBy),
+  }), [notStarted, inProgress, finished, sortBy]);
 
-  const orderedNotDone = useMemo(() => {
-    const byId = new Map((notDone ?? []).map((t) => [String(t.taskId), t]));
-    const ids = [
-      ...notDoneOrder.filter((id) => byId.has(id)),
-      ...Array.from(byId.keys()).filter((id) => !notDoneOrder.includes(id)),
-    ];
-    return ids.map((id) => byId.get(id));
-  }, [notDone, notDoneOrder]);
+  const allTasks = useMemo(() => [
+    ...(notStarted ?? []),
+    ...(inProgress ?? []),
+    ...(finished ?? []),
+  ], [notStarted, inProgress, finished]);
 
-  const orderedDone = useMemo(() => {
-    const byId = new Map((done ?? []).map((t) => [String(t.taskId), t]));
-    const ids = [
-      ...doneOrder.filter((id) => byId.has(id)),
-      ...Array.from(byId.keys()).filter((id) => !doneOrder.includes(id)),
-    ];
-    return ids.map((id) => byId.get(id));
-  }, [done, doneOrder]);
+  function findTask(id) {
+    return allTasks.find((t) => String(t.taskId) === String(id));
+  }
 
-  async function submitNewTask(e) {
-    e.preventDefault();
-    const name = newName.trim();
-    if (!name || submitting) return;
-    setSubmitting(true);
-    try {
-      await onAddTask(name);
-      setNewName("");
-    } finally {
-      setSubmitting(false);
+  async function handleDragEnd({ active, over }) {
+    if (!over) return;
+    const activeId = String(active.id);
+    const overId = String(over.id);
+    if (activeId === overId) return;
+
+    const task = findTask(activeId);
+    if (!task) return;
+
+    if (overId.startsWith("lane:")) {
+      const toStatus = statusForDropId(overId);
+      if (task.status !== toStatus) await onMoveTask(task.taskId, toStatus);
+      return;
+    }
+
+    const overTask = findTask(overId);
+    if (!overTask) return;
+
+    if (task.status !== overTask.status) {
+      await onMoveTask(task.taskId, overTask.status);
     }
   }
 
-  function findTaskById(id) {
-    const all = [...(notDone ?? []), ...(done ?? [])];
-    return all.find((t) => String(t.taskId) === String(id));
-  }
-
-  async function handleDragEnd(event) {
-    const { active, over } = event;
-    if (!over) return;
-
-    const activeId = String(active.id);
-    const overId = String(over.id);
-
-    if (activeId === overId) return;
-
-    const task = findTaskById(activeId);
-    if (!task) return;
-
-    // Moving over a column dropzone
-    if (overId === "lane:notDone" || overId === "lane:done") {
-      const toFinished = overId === "lane:done";
-      if (Boolean(task.isFinished) === toFinished) return;
-      await onMoveTask(task.taskId, toFinished);
-      return;
-    }
-
-    // Sorting within a lane: keep it frontend-only.
-    const overTask = findTaskById(overId);
-    if (!overTask) return;
-
-    const fromLane = laneIdForTask(task);
-    const toLane = laneIdForTask(overTask);
-
-    if (fromLane !== toLane) {
-      // if dropped onto a card in the other lane => treat as lane move
-      await onMoveTask(task.taskId, toLane === "done");
-      return;
-    }
-
-    if (fromLane === "notDone") {
-      const ids = orderedNotDone.map((t) => String(t.taskId));
-      const oldIndex = ids.indexOf(activeId);
-      const newIndex = ids.indexOf(overId);
-      if (oldIndex >= 0 && newIndex >= 0 && oldIndex !== newIndex) {
-        setNotDoneOrder(arrayMove(ids, oldIndex, newIndex));
+  async function handleModalSubmit(payload) {
+    setModalLoading(true);
+    try {
+      if (editingTask) {
+        await onEditTask(editingTask.taskId, payload);
+      } else {
+        await onAddTask(payload);
       }
-    } else {
-      const ids = orderedDone.map((t) => String(t.taskId));
-      const oldIndex = ids.indexOf(activeId);
-      const newIndex = ids.indexOf(overId);
-      if (oldIndex >= 0 && newIndex >= 0 && oldIndex !== newIndex) {
-        setDoneOrder(arrayMove(ids, oldIndex, newIndex));
-      }
+      setModalOpen(false);
+      setEditingTask(null);
+    } finally {
+      setModalLoading(false);
     }
   }
 
+  const total = allTasks.length;
+
   return (
-    <div className="space-y-6">
-      <div className="rounded-xl border border-white/10 bg-neutral-900/40 p-4">
-        <div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
-          <div>
-            <div className="text-sm font-semibold text-white/90">
-              Tickets
-              <span className="ml-2 text-xs font-normal text-white/60">
-                Drag cards left/right to change status.
-              </span>
-            </div>
-            <div className="mt-1 text-xs text-white/55">
-              Tip: grab the <span className="font-semibold text-white/80">drag</span> handle.
-            </div>
+    <div className="space-y-4">
+      <div className="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-white/10 bg-neutral-900/40 px-4 py-3">
+        <div className="flex flex-wrap items-center gap-3 text-xs text-white/70">
+          <span className="rounded-full bg-slate-500/20 px-2 py-1 text-slate-200">
+            Not started: {(notStarted ?? []).length}
+          </span>
+          <span className="rounded-full bg-amber-500/15 px-2 py-1 text-amber-200">
+            In progress: {(inProgress ?? []).length}
+          </span>
+          <span className="rounded-full bg-emerald-500/15 px-2 py-1 text-emerald-200">
+            Finished: {(finished ?? []).length}
+          </span>
+          <span className="text-white/40">/ {total} total</span>
+        </div>
+
+        <div className="flex items-center gap-2">
+          <div className="flex items-center gap-1.5 rounded-lg border border-white/10 bg-black/30 px-2 py-1.5">
+            <MdSort className="size-4 text-white/50" />
+            <span className="text-xs text-white/50">Sort:</span>
+            <select
+              value={sortBy}
+              onChange={(e) => setSortBy(e.target.value)}
+              className="bg-transparent text-xs text-white/80 outline-none cursor-pointer"
+            >
+              <option value="default">Default</option>
+              <option value="dueDate">Due date</option>
+              <option value="priority">Priority</option>
+            </select>
           </div>
 
-          <div className="flex items-center gap-3 text-xs text-white/70">
-            <span className="rounded-full bg-sky-500/15 px-2 py-1 text-sky-200">
-              Not done: {(notDone ?? []).length}
-            </span>
-            <span className="rounded-full bg-emerald-500/15 px-2 py-1 text-emerald-200">
-              Done: {(done ?? []).length}
-            </span>
-          </div>
+          <button
+            onClick={() => { setEditingTask(null); setModalOpen(true); }}
+            className="flex items-center gap-1.5 rounded-lg bg-green-400 px-3 py-1.5 text-sm font-medium text-black hover:bg-green-300"
+          >
+            <MdAdd className="size-4" />
+            New ticket
+          </button>
         </div>
-
-        <form onSubmit={submitNewTask} className="mt-4 flex flex-col gap-3 md:flex-row">
-          <input
-            value={newName}
-            onChange={(e) => setNewName(e.target.value)}
-            placeholder="Add a new ticket (e.g. Fix bug #123)"
-            maxLength={200}
-            className="w-full rounded-lg border border-white/10 bg-black/30 px-3 py-2 text-sm outline-none focus:border-green-400"
-          />
-          <button
-            className="rounded-lg bg-green-400 px-4 py-2 text-sm font-medium text-black disabled:opacity-60"
-            type="submit"
-            disabled={!newName.trim() || submitting}
-          >
-            {submitting ? "Adding..." : "Add"}
-          </button>
-        </form>
       </div>
 
-      <DndContext
-        sensors={sensors}
-        collisionDetection={closestCenter}
-        onDragEnd={handleDragEnd}
-      >
-        <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
-          <KanbanColumn
-            title="Not done"
-            droppableId="lane:notDone"
-            count={orderedNotDone.length}
-          >
-            <SortableContext
-              items={orderedNotDone.map((t) => String(t.taskId))}
-              strategy={rectSortingStrategy}
-            >
-              <div className="space-y-3">
-                {loading ? (
-                  <div className="text-sm opacity-70">Loading...</div>
-                ) : orderedNotDone.length === 0 ? (
-                  <div className="text-sm opacity-70">No tickets yet.</div>
-                ) : (
-                  orderedNotDone.map((t) => (
-                    <TaskTicketCard
-                      key={t.taskId}
-                      task={t}
-                      onEdit={(name) => onEditTask(t.taskId, name)}
-                      onDelete={() => onDeleteTask(t.taskId)}
-                    />
-                  ))
-                )}
-              </div>
-            </SortableContext>
-          </KanbanColumn>
-
-          <KanbanColumn
-            title="Done"
-            droppableId="lane:done"
-            count={orderedDone.length}
-          >
-            <SortableContext
-              items={orderedDone.map((t) => String(t.taskId))}
-              strategy={rectSortingStrategy}
-            >
-              <div className="space-y-3">
-                {loading ? (
-                  <div className="text-sm opacity-70">Loading...</div>
-                ) : orderedDone.length === 0 ? (
-                  <div className="text-sm opacity-70">Nothing done yet.</div>
-                ) : (
-                  orderedDone.map((t) => (
-                    <TaskTicketCard
-                      key={t.taskId}
-                      task={t}
-                      onEdit={(name) => onEditTask(t.taskId, name)}
-                      onDelete={() => onDeleteTask(t.taskId)}
-                    />
-                  ))
-                )}
-              </div>
-            </SortableContext>
-          </KanbanColumn>
+      <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
+        <div className="grid grid-cols-1 gap-4 md:grid-cols-3">
+          {LANES.map((lane) => {
+            const tasks = tasksByLane[lane.id] ?? [];
+            return (
+              <KanbanColumn
+                key={lane.id}
+                laneId={lane.id}
+                title={lane.title}
+                droppableId={lane.droppableId}
+                count={tasks.length}
+              >
+                <SortableContext
+                  items={tasks.map((t) => String(t.taskId))}
+                  strategy={rectSortingStrategy}
+                >
+                  <div className="space-y-3">
+                    {loading ? (
+                      <div className="text-sm opacity-70">Loading...</div>
+                    ) : tasks.length === 0 ? (
+                      <div className="text-sm opacity-40">No tickets.</div>
+                    ) : (
+                      tasks.map((t) => (
+                        <TaskTicketCard
+                          key={t.taskId}
+                          task={t}
+                          onEdit={() => { setEditingTask(t); setModalOpen(true); }}
+                          onDelete={() => onDeleteTask(t.taskId)}
+                        />
+                      ))
+                    )}
+                  </div>
+                </SortableContext>
+              </KanbanColumn>
+            );
+          })}
         </div>
       </DndContext>
+
+      <TaskFormModal
+        isOpen={modalOpen}
+        onClose={() => { setModalOpen(false); setEditingTask(null); }}
+        onSubmit={handleModalSubmit}
+        task={editingTask}
+        isLoading={modalLoading}
+      />
     </div>
   );
 }
-
-
Index: frontend/src/pages/Dashboard/pages/CustomCategory/components/KanbanColumn.jsx
===================================================================
--- frontend/src/pages/Dashboard/pages/CustomCategory/components/KanbanColumn.jsx	(revision 447c39f7111f1415e8f44660911d00bb61568780)
+++ frontend/src/pages/Dashboard/pages/CustomCategory/components/KanbanColumn.jsx	(revision 545738f221bcda7888ec3b35279f55d0f80c4faa)
@@ -1,30 +1,36 @@
 import React, { useMemo } from "react";
 import { useDroppable } from "@dnd-kit/core";
-import { MdCheckCircle, MdRadioButtonUnchecked } from "react-icons/md";
+import { MdCheckCircle, MdRadioButtonUnchecked, MdAutorenew } from "react-icons/md";
 
-export default function KanbanColumn({ title, droppableId, count, children }) {
+const VARIANTS = {
+  NOT_STARTED: {
+    Icon: MdRadioButtonUnchecked,
+    header: "from-slate-500/20 via-slate-500/8 to-transparent",
+    badge: "bg-slate-500/15 text-slate-200 border-slate-500/30",
+    ring: "group-hover:ring-slate-500/25",
+    drop: "border-slate-400/60 bg-slate-400/10",
+    border: "border-slate-500/20",
+  },
+  IN_PROGRESS: {
+    Icon: MdAutorenew,
+    header: "from-amber-500/20 via-amber-500/8 to-transparent",
+    badge: "bg-amber-500/15 text-amber-200 border-amber-500/30",
+    ring: "group-hover:ring-amber-500/25",
+    drop: "border-amber-400/60 bg-amber-400/10",
+    border: "border-amber-500/20",
+  },
+  FINISHED: {
+    Icon: MdCheckCircle,
+    header: "from-emerald-500/25 via-emerald-500/10 to-transparent",
+    badge: "bg-emerald-500/15 text-emerald-200 border-emerald-500/30",
+    ring: "group-hover:ring-emerald-500/25",
+    drop: "border-emerald-400/60 bg-emerald-400/10",
+    border: "border-emerald-500/20",
+  },
+};
+
+export default function KanbanColumn({ laneId, title, droppableId, count, children }) {
   const { setNodeRef, isOver } = useDroppable({ id: droppableId });
-
-  const variant = useMemo(() => {
-    if (droppableId === "lane:done") {
-      return {
-        Icon: MdCheckCircle,
-        header: "from-emerald-500/25 via-emerald-500/10 to-transparent",
-        badge: "bg-emerald-500/15 text-emerald-200 border-emerald-500/30",
-        ring: "group-hover:ring-emerald-500/25",
-        drop: "border-emerald-400/60 bg-emerald-400/10",
-        border: "border-emerald-500/20",
-      };
-    }
-
-    return {
-      Icon: MdRadioButtonUnchecked,
-      header: "from-sky-500/25 via-sky-500/10 to-transparent",
-      badge: "bg-sky-500/15 text-sky-200 border-sky-500/30",
-      ring: "group-hover:ring-sky-500/25",
-      drop: "border-sky-400/60 bg-sky-400/10",
-      border: "border-sky-500/20",
-    };
-  }, [droppableId]);
+  const v = VARIANTS[laneId] ?? VARIANTS.NOT_STARTED;
 
   return (
@@ -32,23 +38,14 @@
       className={
         "group overflow-hidden rounded-xl border bg-neutral-900/40 shadow-sm ring-1 ring-white/5 transition " +
-        variant.border +
-        " " +
-        variant.ring
+        v.border + " " + v.ring
       }
     >
-      <div className={"bg-gradient-to-b " + variant.header + " p-4"}>
+      <div className={"bg-gradient-to-b " + v.header + " p-4"}>
         <div className="flex items-center justify-between gap-3">
           <div className="flex items-center gap-2">
-            <variant.Icon className="size-5 text-white/80" />
-            <h2 className="text-sm font-semibold tracking-wide text-white/90">
-              {title}
-            </h2>
+            <v.Icon className="size-5 text-white/80" />
+            <h2 className="text-sm font-semibold tracking-wide text-white/90">{title}</h2>
           </div>
-          <span
-            className={
-              "rounded-full border px-2 py-0.5 text-xs font-medium " +
-              variant.badge
-            }
-          >
+          <span className={"rounded-full border px-2 py-0.5 text-xs font-medium " + v.badge}>
             {count}
           </span>
@@ -60,5 +57,5 @@
         className={
           "min-h-[220px] p-4 transition-colors " +
-          (isOver ? variant.drop : "border-t border-white/10")
+          (isOver ? v.drop : "border-t border-white/10")
         }
       >
@@ -68,3 +65,2 @@
   );
 }
-
Index: frontend/src/pages/Dashboard/pages/CustomCategory/components/TaskTicketCard.jsx
===================================================================
--- frontend/src/pages/Dashboard/pages/CustomCategory/components/TaskTicketCard.jsx	(revision 447c39f7111f1415e8f44660911d00bb61568780)
+++ frontend/src/pages/Dashboard/pages/CustomCategory/components/TaskTicketCard.jsx	(revision 545738f221bcda7888ec3b35279f55d0f80c4faa)
@@ -1,36 +1,40 @@
-import React, { useMemo, useState } from "react";
+import React, { useMemo } from "react";
 import { useSortable } from "@dnd-kit/sortable";
 import { CSS } from "@dnd-kit/utilities";
-import { MdDelete, MdDragIndicator, MdEdit, MdSave } from "react-icons/md";
+import { MdDelete, MdDragIndicator, MdEdit, MdCalendarToday } from "react-icons/md";
+
+const PRIORITY_STYLES = {
+  HIGH:   "bg-red-500/15 text-red-300 border-red-500/30",
+  MEDIUM: "bg-amber-500/15 text-amber-300 border-amber-500/30",
+  LOW:    "bg-sky-500/15 text-sky-300 border-sky-500/30",
+};
+
+const STATUS_DOT = {
+  NOT_STARTED: "bg-slate-400",
+  IN_PROGRESS: "bg-amber-400",
+  FINISHED:    "bg-emerald-400",
+};
+
+function formatDate(dateStr) {
+  if (!dateStr) return null;
+  const d = new Date(dateStr);
+  return d.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" });
+}
+
+function isOverdue(dateStr) {
+  if (!dateStr) return false;
+  return new Date(dateStr) < new Date(new Date().toDateString());
+}
 
 export default function TaskTicketCard({ task, onEdit, onDelete }) {
   const id = String(task.taskId);
-  const {
-    attributes,
-    listeners,
-    setNodeRef,
-    transform,
+  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
+
+  const style = useMemo(() => ({
+    transform: CSS.Transform.toString(transform),
     transition,
-    isDragging,
-  } = useSortable({ id });
+  }), [transform, transition]);
 
-  const style = useMemo(
-    () => ({
-      transform: CSS.Transform.toString(transform),
-      transition,
-    }),
-    [transform, transition]
-  );
-
-  const [editing, setEditing] = useState(false);
-  const [draft, setDraft] = useState(task.name ?? "");
-
-  async function submitEdit(e) {
-    e.preventDefault();
-    const next = draft.trim();
-    if (!next) return;
-    await onEdit(next);
-    setEditing(false);
-  }
+  const overdue = task.status !== "FINISHED" && isOverdue(task.dueDate);
 
   return (
@@ -40,81 +44,68 @@
       className={
         "group rounded-lg border border-white/10 bg-black/30 p-3 shadow-sm transition hover:border-white/20 hover:bg-black/35 " +
-        (task.isFinished ? "opacity-90" : "") +
-        (isDragging ? " opacity-60" : "")
+        (task.status === "FINISHED" ? "opacity-80" : "") +
+        (isDragging ? " opacity-50 scale-95" : "")
       }
     >
-      <div className="flex items-start justify-between gap-3">
+      {/* Header row */}
+      <div className="flex items-start justify-between gap-2">
         <div className="min-w-0 flex-1">
-          {editing ? (
-            <form onSubmit={submitEdit} className="flex gap-2">
-              <input
-                value={draft}
-                onChange={(e) => setDraft(e.target.value)}
-                maxLength={200}
-                className="w-full rounded-md border border-white/10 bg-black/40 px-2 py-1 text-sm outline-none focus:border-green-400"
-                autoFocus
-              />
-              <button
-                type="submit"
-                className="inline-flex items-center gap-1 rounded-md bg-green-400 px-2 py-1 text-sm font-medium text-black"
-              >
-                <MdSave className="size-4" />
-                Save
-              </button>
-            </form>
-          ) : (
-            <div className="flex items-center gap-2">
-              <span
-                className={
-                  "h-2 w-2 shrink-0 rounded-full " +
-                  (task.isFinished ? "bg-emerald-400" : "bg-sky-400")
-                }
-              />
-              <div className="min-w-0 text-sm font-medium text-white/90">
-                {task.name}
-              </div>
-            </div>
-          )}
+          <div className="flex items-center gap-2">
+            <span className={"h-2 w-2 shrink-0 rounded-full " + (STATUS_DOT[task.status] ?? "bg-slate-400")} />
+            <span className="text-sm font-medium text-white/90 leading-snug">{task.name}</span>
+          </div>
 
-          <div className="mt-1 text-xs text-white/50">Ticket #{task.taskId}</div>
+          {task.description ? (
+            <p className="mt-1.5 text-xs text-white/50 line-clamp-2">{task.description}</p>
+          ) : null}
         </div>
 
-        <div className="flex shrink-0 items-center gap-2">
-          {!editing ? (
-            <button
-              type="button"
-              onClick={() => {
-                setDraft(task.name ?? "");
-                setEditing(true);
-              }}
-              className="rounded-md p-1 text-white/70 hover:bg-white/10 hover:text-white"
-              title="Edit"
-            >
-              <MdEdit className="size-5" />
-            </button>
-          ) : null}
-
+        <div className="flex shrink-0 items-center gap-1">
+          <button
+            type="button"
+            onClick={onEdit}
+            className="rounded-md p-1 text-white/50 hover:bg-white/10 hover:text-white"
+            title="Edit"
+          >
+            <MdEdit className="size-4" />
+          </button>
           <button
             type="button"
             onClick={onDelete}
-            className="rounded-md p-1 text-white/70 hover:bg-white/10 hover:text-white"
+            className="rounded-md p-1 text-white/50 hover:bg-white/10 hover:text-white"
             title="Delete"
           >
-            <MdDelete className="size-5" />
+            <MdDelete className="size-4" />
           </button>
-
           <button
             type="button"
-            className="inline-flex cursor-grab items-center gap-1 rounded-md p-1 text-white/70 hover:bg-white/10 hover:text-white active:cursor-grabbing"
+            className="cursor-grab rounded-md p-1 text-white/50 hover:bg-white/10 hover:text-white active:cursor-grabbing"
             title="Drag"
             {...attributes}
             {...listeners}
           >
-            <MdDragIndicator className="size-5" />
+            <MdDragIndicator className="size-4" />
           </button>
         </div>
+      </div>
+
+      {/* Footer row: priority + due date */}
+      <div className="mt-2 flex flex-wrap items-center gap-2">
+        <span className="text-xs text-white/30">#{task.taskId}</span>
+
+        {task.priority ? (
+          <span className={"rounded-full border px-2 py-0.5 text-xs font-medium " + (PRIORITY_STYLES[task.priority] ?? PRIORITY_STYLES.LOW)}>
+            {task.priority.charAt(0) + task.priority.slice(1).toLowerCase()}
+          </span>
+        ) : null}
+
+        {task.dueDate ? (
+          <span className={"flex items-center gap-1 text-xs " + (overdue ? "text-red-400" : "text-white/40")}>
+            <MdCalendarToday className="size-3" />
+            {overdue ? "Overdue · " : ""}{formatDate(task.dueDate)}
+          </span>
+        ) : null}
       </div>
     </div>
   );
 }
-
