data-table

✅ 1. Masalah Edit dan Delete Belum Berfungsi
Di getUserColumns, kamu cuma nge-console.log saat tombol edit/delete diklik:

const columns = getUserColumns(
  (r) => console.log("Update", r),
  (r) => console.log("Delete", r)
);

🔧 Solusi:
Tambahkan state dan handler di UserTable untuk memicu edit/delete yang sesungguhnya:

const [editingUser, setEditingUser] = useState<User | null>(null);

const handleUpdateClick = (user: User) => {
  setEditingUser(user);
  setUSername(user.username);
  setPassword(user.password);
  setNo(user.no_hp);
  setStatus(user.status);
  setAlamat(user.alamat);
  setEmail(user.email.toString());
};

const handleDeleteClick = async (user: User) => {
  const token = localStorage.getItem("adminToken");
  try {
    const res = await fetch(`http://localhost:8000/api/user/${user.username}`, {
      method: "DELETE",
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });
    if (!res.ok) throw new Error("Gagal hapus user");

    setData((prev) => prev.filter((u) => u.username !== user.username));
  } catch (err) {
    console.log("Delete Error", err);
  }
};

Ganti pemanggilan fungsi ini di bawah:

const columns = getUserColumns(handleUpdateClick, handleDeleteClick);

✅ 2. Tombol Submit Tidak Memperbarui User Jika Mode Edit

Sekarang handleSubmit hanya menambah user baru. Kalau kamu sedang edit, perlu membedakan logic-nya.

🔧 Solusi:

Modifikasi handleSubmit:

const handleSubmit = async () => {
  const formData = new FormData();
  formData.append("username", username);
  formData.append("password", password);
  formData.append("no_hp", no_hp);
  formData.append("status", status);
  formData.append("alamat", alamat);
  formData.append("email", email);

  const token = localStorage.getItem("adminToken");

  try {
    const url = editingUser
      ? `http://127.0.0.1:8000/api/user/${editingUser.username}`
      : "http://127.0.0.1:8000/api/user/";
    const method = editingUser ? "PUT" : "POST";

    const response = await fetch(url, {
      method,
      headers: {
        Authorization: `Bearer ${token}`,
      },
      body: formData,
    });

    if (!response.ok) {
      throw new Error("Gagal simpan data");
    }

    const newUser = await response.json();

    if (editingUser) {
      // Update data di list
      setData((prev) =>
        prev.map((u) => (u.username === editingUser.username ? newUser : u))
      );
    } else {
      // Tambahkan user baru
      setData((prev) => [...prev, newUser]);
    }

    // Reset form
    setUSername("");
    setPassword("");
    setStatus("");
    setAlamat("");
    setNo("");
    setEmail("");
    setEditingUser(null);
  } catch (err) {
    console.log("Submit Error", err);
  }
};

✅ 3. Tampilkan Nama Dialog Berubah Saat Edit

Ganti bagian ini:

<DialogTitle>Tambahkan UserDialogTitle>

Menjadi:

<DialogTitle>{editingUser ? "Edit User" : "Tambahkan User"}DialogTitle>

✅ 4. Bug Minor: Tipe Email

Kamu pakai email: number; di User, padahal email itu string.

export type User = {
  username: string;
  password: string;
  email: string; // ini sebelumnya number
  no_hp: string;
  status: string;
  alamat: string;
};

Kalau kamu butuh bantuin struktur update endpoint dari backend biar cocok (PUT dengan FormData), tinggal bilang aja ya.

Mau sekalian ku bantuin styling atau pop-up konfirmasi delete juga?

1. Menu
"use client"
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { PlusIcon } from "lucide-react";
import {
  Dialog,
  DialogTrigger,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogFooter,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";

import { useEffect, useState } from "react";

interface Menuitem {
  id_menu: number;
  nama_menu: string;
  harga: number;
  menu_image: string;
  id_kategori:number;
}

const Features05Page = () => {
  const [menus, setMenu] = useState([]);
  const [namaMenu, setNamaMenu] = useState("");
  const [harga, setHarga] = useState("");
  const [imageFile, setImageFile] = useState(null);
  const [kategori,setKategori]=useState(1)


  const handleSubmit = async () => {
    const token = localStorage.getItem("adminToken");
    const formData = new FormData();
    formData.append("nama_menu", namaMenu);
    formData.append("harga", harga);
    formData.append("id_kategori", kategori.toString()); // pastikan string

    if (imageFile) {
      formData.append("menu_image", imageFile);
    }

    try {
      const response = await fetch("http://127.0.0.1:8000/api/menu/", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${token}`,
          // jangan tambahkan content-type, biarkan browser mengatur sendiri
        },
        body: formData,
      });

      if (!response.ok) {
        throw new Error("Gagal menambahkan menu");
      }

      const data = await response.json();
      setMenu((prev) => [...prev, data]); // update UI langsung
      setNamaMenu("");
      setHarga("");
      setKategori(1)
      setImageFile(null);
    } catch (error) {
      console.error("Error:", error);
    }
  };
  useEffect(() => {
    const token = localStorage.getItem("adminToken");
    fetch("http://127.0.0.1:8000/api/menu/", {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    })
      .then((response) => response.json())
      .then((data: Menuitem[]) => setMenu(data))
      .catch((error) => console.log("error", error));
  }, []);

  return (
    
      
        Daftar Menu
      
      
        
          
             Tambah Menu
          
        
        
          
            Tambah Menu Baru
          

          
            
              
                Nama
              
               setNamaMenu(e.target.value)}
                className="col-span-3"
              />
            
            
              
                Harga
              
               setHarga(e.target.value)}
                className="col-span-3"
              />
            
            
              
                kategori
              
               setKategori(parseInt(e.target.value))}
                className="col-span-3"
              />
            
            
              
                Gambar
              
               setImageFile(e.target.files?.[0] || null)}
                className="col-span-3"
              />
            
          

          
            Simpan
          
        
      
      
        {menus.map((menu) => (
          
            
              
                {menu.nama_menu}
              
              
                {menu.harga}
              
            
            
              
                
              
            
          
        ))}
      
    
  );
};

export default Features05Page;
  1. Meja
import { useEffect, useState } from "react";
import { Button } from "../ui/button";
import {
  Dialog,
  DialogContent,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "../ui/dialog";
import { Input } from "../ui/input";
import { Label } from "@/components/ui/label";
import { PlusIcon } from "lucide-react";

const features = [
  {
    title: "Identify Opportunities",
    description: "Find untapped areas to explore effortlessly.",
  },
  {
    title: "Build Authority",
    description: "Craft content that resonates and inspires trust.",
  },
  {
    title: "Instant Insights",
    description: "Get actionable insights instantly at a glance.",
  },
];

interface MejaItem {
  id_meja: number;
  no_meja: number;
  kapasitas: number;
  image_meja: string;
}
const Features02Page = () => {
  const [mejas, setMeja] = useState([]);
  const [no_meja, setNomeja] = useState("");
  const [id_meja, setIDMeja] = useState("");
  const [kapasitas, setKapasitas] = useState("");
  const [imageMeja, setImageMeja] = useState(null);
  const [open, setOpen] = useState(false);

  useEffect(() => {
    const token = localStorage.getItem("adminToken");

    try {
      fetch("http://127.0.0.1:8000/api/meja/", {
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
        .then((response) => response.json())
        .then((data: MejaItem[]) => setMeja(data))
        .catch((error) => console.log("error ", error));
    } catch (error) {
      console.log("error ", error);
    }
  }, []);

  const handleSubmit = async () => {
    const token = localStorage.getItem("adminToken");
    const newForm = new FormData();

    newForm.append("no_meja", no_meja);
    newForm.append("kapasitas", kapasitas);
    if (imageMeja) {
      newForm.append("image_meja", imageMeja);
    }

    try {
      const response = await fetch("http://127.0.0.1:8000/api/meja/", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${token}`,
        },
        body: newForm,
      });
      if (!response.ok) {
        throw new Error("Gagal menambahkan meja");
      }
      const data = await response.json();
      setMeja((prev) => [...prev, data]);
      setNomeja("");
      setKapasitas("");
      setImageMeja(null);
      setOpen(false);
    } catch (error) {
      console.log(error);
    }
  };
  return (
    
      
        
          Daftar Meja
        
        
          
            
              
                 Tambah Menu
              
            
            
              
                Tambah Menu Baru
              

              
                
                  
                    No Meja
                  
                   setNomeja(e.target.value)}
                    className="col-span-3"
                  />
                
                
                  
                    Kapasitas
                  
                   setKapasitas(e.target.value)}
                    className="col-span-3"
                  />
                

                
                  
                    Gambar
                  
                   setImageMeja(e.target.files?.[0] || null)}
                    className="col-span-3"
                  />
                
              

              
                Simpan
              
            
          
        
        
          {mejas.map((meja) => (
            
              
                
              
              
                Nomer Meja : {meja.no_meja}
              
              
                Kapasitas : {meja.kapasitas}
              
            
          ))}
        
      
    
  );
};

export default Features02Page;
  1. Reservasi
// page
"use client"

import React from "react"
import { ReservationTable } from "./data-table"

export default function ReservationPage() {
  return (
    
      Reservation List
      
    
  )
}
// column.tsx


import { ColumnDef } from "@tanstack/react-table"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { ArrowUpDown, Edit, Trash2 } from "lucide-react"
import { Reservation } from "./data-table"

export function getReservationColumns(
  handleUpdateClick: (reservation: Reservation) => void,
  handleDeleteClick: (reservation: Reservation) => void
): ColumnDef[] {
  return [
    {
      id: "select",
      header: ({ table }) => (
         table.toggleAllPageRowsSelected(!!value)}
          aria-label="Select all"
        />    
      ),
      cell: ({ row }) => (
         row.toggleSelected(!!value)}
          aria-label="Select row"
        />
      ),
      enableSorting: false,
      enableHiding: false,
    },
    {
      header: "ID",
      cell: ({ row }) => {row.index + 1},
    },
    {
      accessorKey: "nama_customer",
      header: ({ column }) => (
         column.toggleSorting(column.getIsSorted() === "asc")}
        >
          Customer Name
          
        
      ),
      cell: ({ row }) => {row.getValue("nama_customer")},
    },
    {
      accessorKey: "tanggal_reservasi",
      header: ({ column }) => (
         column.toggleSorting(column.getIsSorted() === "asc")}
        >
          Date
          
        
      ),
      cell: ({ row }) => {
        const date = new Date(row.getValue("tanggal_reservasi"))
        return {date.toLocaleDateString("en-GB")}
      },
    },
    {
      accessorKey: "status",
      header: "Status",
      cell: ({ row }) => {
        const status = row.getValue("status") as string
        const statusClass = {
          booked: "text-blue-600",
          confirmed: "text-green-600",
          pending: "text-amber-600",
          cancelled: "text-red-600",
        }[status.toLowerCase()] || ""
        return {status}
      },
    },
    {
      accessorKey: "id_meja",
      header: "Table No.",
      cell: ({ row }) => Table {row.getValue("id_meja")},
    },
    {
      id: "actions",
      enableHiding: false,
      header: "Actions",
      cell: ({ row }) => {
        const reservation = row.original
        return (
          
             handleUpdateClick(reservation)}
            >
              
            
             handleDeleteClick(reservation)}
            >
              
            
          
        )
      },
    },
  ]
}
// data table

"use client";

import React, { use, useState } from "react";
import {
  ColumnFiltersState,
  SortingState,
  VisibilityState,
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useReactTable,
} from "@tanstack/react-table";
import { Input } from "@/components/ui/input";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import { getReservationColumns } from "./column";
import { Button } from "@/components/ui/button";
import { PlusIcon } from "lucide-react";
import {
  Dialog,
  DialogContent,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
import { DialogFooter, DialogHeader } from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { log } from "console";
import { Select, SelectContent, SelectItem } from "@/components/ui/select";
import { SelectTrigger, SelectValue } from "@radix-ui/react-select";

export type Reservation = {
  id_reservasi: number;
  nama_customer: string;
  tanggal_reservasi: string;
  status: string;
  id_meja: number;
  id_admin: number;
};

export function ReservationTable() {
  const [data, setData] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState(null);
  const [sorting, setSorting] = React.useState([]);
  const [columnFilters, setColumnFilters] = React.useState(
    []
  );
  const [columnVisibility, setColumnVisibility] =
    React.useState({});
  const [rowSelection, setRowSelection] = React.useState({});
  const [id_admin, setIdAdmin] = useState("");
  const [nama_customer, setNama] = useState("");
  const [tanggal_reservasi, setTanggal] = useState("");
  const [status, setStatus] = useState("");
  const [id_meja, setMeja] = useState("");

  const fetchReservations = async () => {
    try {
      setLoading(true);
      const token = localStorage.getItem("adminToken");

      const response = await fetch("http://localhost:8000/api/reservasi/", {
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${token}`,
        },
      });
      if (!response.ok) throw new Error("Failed to fetch reservation data");
      const result = await response.json();
      setData(Array.isArray(result) ? result : [result]);
    } catch (err) {
      setError("Failed to load reservations. Please try again later.");
    } finally {
      setLoading(false);
    }
  };

  React.useEffect(() => {
    fetchReservations();
  }, []);

  const handleSubmit = async () => {
    const formData = new FormData();
    formData.append("id_admin", id_admin);
    formData.append("nama_customer", nama_customer);
    formData.append("tanggal_reservasi", tanggal_reservasi);
    formData.append("status", status);
    formData.append("id_meja", id_meja);
    const token = localStorage.getItem("adminToken");

    try {
      const response = await fetch("http://127.0.0.1:8000/api/reservasi/", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${token}`,
        },
        body: formData,
      });
      if (!response.ok) {
        throw new Error("Gagal menambahkan reservasi");
      }
      const data = await response.json();
      setData((prev) => [...prev, data]);
      setIdAdmin("");
      setNama("");
      setStatus("");
      setTanggal("");
      setMeja("");
    } catch (err) {
      console.log("Error ", err);
    }
  };

  const columns = getReservationColumns(
    (r) => console.log("Update", r),
    (r) => console.log("Delete", r)
  );

  const table = useReactTable({
    data,
    columns,
    onSortingChange: setSorting,
    onColumnFiltersChange: setColumnFilters,
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    onColumnVisibilityChange: setColumnVisibility,
    onRowSelectionChange: setRowSelection,
    state: {
      sorting,
      columnFilters,
      columnVisibility,
      rowSelection,
    },
  });

  if (loading)
    return (
      
        Loading reservation data...
      
    );
  if (error)
    return (
      
        {error}
      
    );

  return (
    
      
        
            table.getColumn("nama_customer")?.setFilterValue(e.target.value)
          }
          className="max-w-sm"
        />
        
          
            
               Tambah Reservasi
            
          
          
            
              Tambahkan Reservasi
            
            
              
                Id Admin
              
               setIdAdmin(e.target.value)}
                className="col-span-3"
              />
            
            
              
                Id Meja
              
               setMeja(e.target.value)}
                className="col-span-3"
              />
            
            
              
                Nama
              
               setNama(e.target.value)}
                className="col-span-3"
              />
            
            
              
                Status
              
              
                 
                  
                
                
                  Pending
                  Booked
                  Success
                  Cancelled
                
              
            
            
              
                Tanggal
              
               setTanggal(e.target.value)}
                className="col-span-3"
                type="date"
              />
            

            
              Simpan
            
          
        
      
      
        
          
            {table.getHeaderGroups().map((headerGroup) => (
              
                {headerGroup.headers.map((header) => (
                  
                    {header.isPlaceholder
                      ? null
                      : flexRender(
                          header.column.columnDef.header,
                          header.getContext()
                        )}
                  
                ))}
              
            ))}
          

          
            {table.getRowModel().rows?.length ? (
              table.getRowModel().rows.map((row) => (
                
                  {row.getVisibleCells().map((cell) => (
                    
                      {flexRender(
                        cell.column.columnDef.cell,
                        cell.getContext()
                      )}
                    
                  ))}
                
              ))
            ) : (
              
                
                  No results.
                
              
            )}
          
        
      
    
  );
}
  1. Report
'use client'

import ProductCard from "@/components/reportcard"
import { DataTableDemo } from "./table"

export default function Report(){
    return(
        
            
            
            

            
        
    )
}

// table.tsx
"use client"

import React, { useEffect, useState } from "react"
import {
  ColumnDef,
  ColumnFiltersState,
  SortingState,
  VisibilityState,
  flexRender,
  getCoreRowModel,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useReactTable,
} from "@tanstack/react-table"
import { ArrowUpDown, ChevronDown, MoreHorizontal } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input"
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table"

// 1. Tipe data
export type Payment = {
  id: string
  nama_customer: string
  grand_total: number
  status_pembayaran: "Pending" | "Processing" | "Success" | "Failed"
}

// 2. Kolom tetap sama
export const columns: ColumnDef[] = [
  {
    id: "select",
    header: ({ table }) => (
       table.toggleAllPageRowsSelected(!!value)}
        aria-label="Select all"
      />
    ),
    cell: ({ row }) => (
       row.toggleSelected(!!value)}
        aria-label="Select row"
      />
    ),
    enableSorting: false,
    enableHiding: false,
  },
  {
    accessorKey: "status_pembayaran",
    header: "Status",
    cell: ({ row }) => (
      {row.getValue("status_pembayaran")}
    ),
  },
  {
    accessorKey: "nama_customer",
    header: ({ column }) => (
       column.toggleSorting(column.getIsSorted() === "asc")}
      >
        Nama Customer
        
      
    ),
    cell: ({ row }) => {row.getValue("nama_customer")},
  },
  {
    accessorKey: "grand_total",
    header: () => Amount,
    cell: ({ row }) => {
      const amount = parseFloat(row.getValue("grand_total"))

      return {amount}
    },
  },
  {
    id: "actions",
    enableHiding: false,
    cell: ({ row }) => {
      const payment = row.original
      return (
        
          
            
              Open menu
              
            
          
          
            Actions
             navigator.clipboard.writeText(payment.id)}
            >
              Copy payment ID
            
            
            View customer
            View payment details
          
        
      )
    },
  },
]

// 3. Komponen utama
export function DataTableDemo() {
  const [data, setData] = useState([])
  const [loading, setLoading] = useState(true)

  const [sorting, setSorting] = useState([])
  const [columnFilters, setColumnFilters] = useState([])
  const [columnVisibility, setColumnVisibility] = useState({})
  const [rowSelection, setRowSelection] = useState({})

  // 4. Fetch API saat mount
  useEffect(() => {
    const fetchData = async () => {
        const token = localStorage.getItem("adminToken")
      try {
        const res = await fetch("http://127.0.0.1:8000/api/report/",{
            headers:{
                Authorization : `Bearer ${token}`
            }
        })
        const json = await res.json()
        setData(json)
      } catch (error) {
        console.error("Failed to fetch data", error)
      } finally {
        setLoading(false)
      }
    }

    fetchData()
  }, [])

  const table = useReactTable({
    data,
    columns,
    onSortingChange: setSorting,
    onColumnFiltersChange: setColumnFilters,
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    getSortedRowModel: getSortedRowModel(),
    getFilteredRowModel: getFilteredRowModel(),
    onColumnVisibilityChange: setColumnVisibility,
    onRowSelectionChange: setRowSelection,
    state: {
      sorting,
      columnFilters,
      columnVisibility,
      rowSelection,
    },
  })

  return (
    
      
        
            table.getColumn("nama_customer")?.setFilterValue(event.target.value)
          }
          className="max-w-sm"
        />
        
          
            
              Columns 
            
          
          
            {table
              .getAllColumns()
              .filter((column) => column.getCanHide())
              .map((column) => (
                
                    column.toggleVisibility(!!value)
                  }
                >
                  {column.id}
                
              ))}
          
        
      

      
        
          
            {table.getHeaderGroups().map((headerGroup) => (
              
                {headerGroup.headers.map((header) => (
                  
                    {header.isPlaceholder
                      ? null
                      : flexRender(
                          header.column.columnDef.header,
                          header.getContext()
                        )}
                  
                ))}
              
            ))}
          
          
            {loading ? (
              
                
                  Loading...
                
              
            ) : table.getRowModel().rows?.length ? (
              table.getRowModel().rows.map((row) => (
                
                  {row.getVisibleCells().map((cell) => (
                    
                      {flexRender(cell.column.columnDef.cell, cell.getContext())}
                    
                  ))}
                
              ))
            ) : (
              
                
                  No results.
                
              
            )}
          
        
      

      
        
          {table.getFilteredSelectedRowModel().rows.length} of{" "}
          {table.getFilteredRowModel().rows.length} row(s) selected.
        
        
           table.previousPage()}
            disabled={!table.getCanPreviousPage()}
          >
            Previous
          
           table.nextPage()}
            disabled={!table.getCanNextPage()}
          >
            Next
          
        
      
    
  )
}