```import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

export interface Property {
id: number;
propertyTitle: string;
agencyId: number;
propertyTypeId: number;
propertyTypeName: string;
description: string;
amenities: string;
monthlyRent: number;
location: string;
unitsAvailable: number;
applicationDeadline: string;
status: string;
createdBy: string;
createdDate: string;
isDeleted: boolean;
deletedDate: string;
deletedBy: string;
updatedDate: string;
updatedBy: string;
}

export interface PropertyResponse {
data: Property[];
message: string;
totalCount: number;
pageNumber: number;
pageSize: number;
}

export interface GetAllPropertyParams {
pageSize: number;
pageNumber: number;
searchTerm?: string;
statusFilter?: string;
}

export interface DeleteResponse {
response: string;
}

export const propertyApi = createApi({
reducerPath: "propertyApi",

baseQuery: fetchBaseQuery({
baseUrl: "https://localhost:7083/api/",

prepareHeaders: (headers) => {
  const token = localStorage.getItem("token");

  if (token) {
    headers.set("Authorization", `Bearer ${token}`);
  }

  headers.set("Content-Type", "application/json");

  return headers;
},

Enter fullscreen mode Exit fullscreen mode

}),

tagTypes: ["Property"],

endpoints: (builder) => ({
// ==========================================
// GET ALL PROPERTY
// ==========================================
getAllProperty: builder.query<
PropertyResponse,
GetAllPropertyParams
>({
query: ({
pageSize,
pageNumber,
searchTerm = "",
statusFilter = "",
}) => ({
url: "Property/GetAllProperty",
method: "GET",
params: {
PageSize: pageSize,
PageNumber: pageNumber,
SearchTerm: searchTerm,
StatusFilter: statusFilter,
},
}),

  providesTags: ["Property"],
}),

// ==========================================
// DELETE PROPERTY
// ==========================================
deleteProperty: builder.mutation<
  DeleteResponse,
  number
>({
  query: (propertyId) => ({
    url: "Property/DeleteProperty",
    method: "DELETE",
    body: {
      propertyId,
    },
  }),

  invalidatesTags: ["Property"],
}),

Enter fullscreen mode Exit fullscreen mode

}),
});

export const {
useGetAllPropertyQuery,
useDeletePropertyMutation,
} = propertyApi;
// Table
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import {
FaEdit,
FaTrash,
FaSearch,
FaExclamationTriangle,
FaTimes,
} from "react-icons/fa";

import {
useGetAllPropertyQuery,
useDeletePropertyMutation,
} from "../services/propertyApi";

const PAGE_SIZE = 10;

const PropertyTable = () => {
const navigate = useNavigate();

// ==========================================
// PAGINATION
// ==========================================
const [pageNumber, setPageNumber] = useState(1);

// ==========================================
// SEARCH
// ==========================================
const [searchInput, setSearchInput] = useState("");
const [searchTerm, setSearchTerm] = useState("");

// ==========================================
// STATUS FILTER
// ==========================================
const [statusFilter, setStatusFilter] = useState("");

// ==========================================
// DELETE MODAL
// ==========================================
const [deletePropertyId, setDeletePropertyId] =
useState(null);

// ==========================================
// ROLE
// ==========================================
const role = localStorage.getItem("role");

const isTenant =
role?.toLowerCase() === "tenant";

// ==========================================
// GET PROPERTIES
// ==========================================
const {
data,
isLoading,
isFetching,
isError,
} = useGetAllPropertyQuery({
pageSize: PAGE_SIZE,
pageNumber,
searchTerm,
statusFilter,
});

// ==========================================
// DELETE MUTATION
// ==========================================
const [
deleteProperty,
{
isLoading: isDeleting,
},
] = useDeletePropertyMutation();

// ==========================================
// DEBOUNCE SEARCH
// ==========================================
useEffect(() => {
const timer = setTimeout(() => {
setPageNumber(1);
setSearchTerm(searchInput);
}, 500);

return () => clearTimeout(timer);

Enter fullscreen mode Exit fullscreen mode

}, [searchInput]);

// ==========================================
// DATA
// ==========================================
const properties = data?.data ?? [];

const totalCount =
data?.totalCount ?? 0;

const totalPages = Math.ceil(
totalCount / PAGE_SIZE
);

// ==========================================
// APPLY
// ==========================================
const handleApply = (
propertyId: number
) => {
navigate(
/application-form/${propertyId}
);
};

// ==========================================
// EDIT
// ==========================================
const handleEdit = (
propertyId: number
) => {
console.log(
"Edit Property ID:",
propertyId
);

// Future:
// navigate(`/property/edit/${propertyId}`);

Enter fullscreen mode Exit fullscreen mode

};

// ==========================================
// OPEN DELETE MODAL
// ==========================================
const handleDeleteClick = (
propertyId: number
) => {
setDeletePropertyId(propertyId);
};

// ==========================================
// CLOSE DELETE MODAL
// ==========================================
const handleCancelDelete = () => {
if (isDeleting) return;

setDeletePropertyId(null);

Enter fullscreen mode Exit fullscreen mode

};

// ==========================================
// CONFIRM DELETE
// ==========================================
const handleConfirmDelete = async () => {
if (deletePropertyId === null) {
return;
}

try {
  const result =
    await deleteProperty(
      deletePropertyId
    ).unwrap();

  console.log(
    result.response
  );

  // Close modal
  setDeletePropertyId(null);

  /*
   * Because deleteProperty has:
   *
   * invalidatesTags: ["Property"]
   *
   * RTK Query automatically refetches
   * the property list.
   */
} catch (error) {
  console.error(
    "Failed to delete property:",
    error
  );
}

Enter fullscreen mode Exit fullscreen mode

};

// ==========================================
// STATUS STYLE
// ==========================================
const getStatusStyle = (
status: string
) => {
switch (
status.toLowerCase()
) {
case "available":
return "bg-green-100 text-green-700";

  case "rented":
    return "bg-red-100 text-red-700";

  case "onhold":
    return "bg-yellow-100 text-yellow-700";

  default:
    return "bg-gray-200 text-gray-700";
}

Enter fullscreen mode Exit fullscreen mode

};

// ==========================================
// LOADING
// ==========================================
if (isLoading) {
return (


Loading properties...

);
}

// ==========================================
// ERROR
// ==========================================
if (isError) {
return (

Failed to load properties.

);
}

return (
<>
{/* ========================================
PROPERTY TABLE
======================================== */}

    {/* HEADER */}
    <div
      className="flex flex-col gap-4
                 border-b border-gray-300
                 px-6 py-4
                 md:flex-row
                 md:items-center
                 md:justify-between"
    >
      <h2 className="text-xl font-semibold text-gray-900">
        Properties
      </h2>

      {/* SEARCH */}
      <div className="relative w-full md:w-80">
        <FaSearch
          className="absolute left-3 top-1/2
                     -translate-y-1/2
                     text-gray-500"
          size={14}
        />

        <input
          type="text"
          value={searchInput}
          onChange={(e) =>
            setSearchInput(
              e.target.value
            )
          }
          placeholder="Search properties..."
          className="w-full rounded-lg
                     border border-gray-400
                     bg-white
                     py-2 pl-9 pr-3
                     text-gray-900
                     outline-none
                     placeholder:text-gray-500
                     focus:border-blue-500
                     focus:ring-2
                     focus:ring-blue-200"
        />
      </div>

      {/* STATUS FILTER */}
      <select
        value={statusFilter}
        onChange={(e) => {
          setStatusFilter(
            e.target.value
          );

          setPageNumber(1);
        }}
        className="rounded-lg
                   border border-gray-400
                   bg-white
                   px-3 py-2
                   text-gray-800
                   outline-none
                   focus:border-blue-500
                   focus:ring-2
                   focus:ring-blue-200"
      >
        <option value="">
          All Status
        </option>

        <option value="Available">
          Available
        </option>

        <option value="Rented">
          Rented
        </option>

        <option value="OnHold">
          OnHold
        </option>
      </select>
    </div>

    {/* TABLE */}
    <div className="overflow-x-auto">
      <table className="w-full text-left text-sm">

        {/* TABLE HEADER */}
        <thead className="bg-gray-300 text-gray-900">
          <tr>
            <th className="whitespace-nowrap px-6 py-4 font-semibold">
              Property Name
            </th>

            <th className="whitespace-nowrap px-6 py-4 font-semibold">
              Monthly Rent
            </th>

            <th className="whitespace-nowrap px-6 py-4 font-semibold">
              Location
            </th>

            <th className="whitespace-nowrap px-6 py-4 font-semibold">
              Units Available
            </th>

            <th className="whitespace-nowrap px-6 py-4 font-semibold">
              Application Deadline
            </th>

            <th className="whitespace-nowrap px-6 py-4 font-semibold">
              Status
            </th>

            {isTenant && (
              <th className="px-6 py-4 text-center font-semibold">
                Apply
              </th>
            )}

            {!isTenant && (
              <th className="px-6 py-4 text-center font-semibold">
                Action
              </th>
            )}
          </tr>
        </thead>

        {/* TABLE BODY */}
        <tbody className="divide-y divide-gray-300">

          {properties.length === 0 ? (
            <tr>
              <td
                colSpan={7}
                className="px-6 py-12 text-center"
              >
                <p className="text-gray-700">
                  No properties found.
                </p>
              </td>
            </tr>
          ) : (
            properties.map(
              (property) => (
                <tr
                  key={property.id}
                  className="transition hover:bg-gray-100"
                >

                  {/* PROPERTY NAME */}
                  <td className="px-6 py-4 font-medium text-gray-900">
                    {property.propertyTitle}
                  </td>

                  {/* RENT */}
                  <td className="px-6 py-4 font-medium text-gray-800">
                    ₹
                    {property.monthlyRent.toLocaleString(
                      "en-IN"
                    )}
                  </td>

                  {/* LOCATION */}
                  <td className="px-6 py-4 text-gray-700">
                    {property.location}
                  </td>

                  {/* UNITS */}
                  <td className="px-6 py-4 text-gray-700">
                    {property.unitsAvailable}
                  </td>

                  {/* DEADLINE */}
                  <td className="px-6 py-4 text-gray-700">
                    {new Date(
                      property.applicationDeadline
                    ).toLocaleDateString(
                      "en-IN"
                    )}
                  </td>

                  {/* STATUS */}
                  <td className="px-6 py-4">
                    <span
                      className={`inline-flex
                        rounded-full
                        px-3 py-1
                        text-xs
                        font-semibold
                        ${getStatusStyle(
                          property.status
                        )}`}
                    >
                      {property.status}
                    </span>
                  </td>

                  {/* TENANT */}
                  {isTenant && (
                    <td className="px-6 py-4 text-center">
                      <button
                        type="button"
                        onClick={() =>
                          handleApply(
                            property.id
                          )
                        }
                        className="rounded-lg
                                   bg-blue-600
                                   px-4 py-2
                                   font-medium
                                   text-white
                                   transition
                                   hover:bg-blue-700"
                      >
                        Apply
                      </button>
                    </td>
                  )}

                  {/* ADMIN / MANAGER */}
                  {!isTenant && (
                    <td className="px-6 py-4">
                      <div
                        className="flex
                                   items-center
                                   justify-center
                                   gap-4"
                      >

                        {/* EDIT */}
                        <button
                          type="button"
                          title="Edit Property"
                          onClick={() =>
                            handleEdit(
                              property.id
                            )
                          }
                          className="text-blue-600
                                     transition
                                     hover:text-blue-800"
                        >
                          <FaEdit
                            size={18}
                          />
                        </button>

                        {/* DELETE */}
                        <button
                          type="button"
                          title="Delete Property"
                          onClick={() =>
                            handleDeleteClick(
                              property.id
                            )
                          }
                          className="text-red-600
                                     transition
                                     hover:text-red-800"
                        >
                          <FaTrash
                            size={18}
                          />
                        </button>

                      </div>
                    </td>
                  )}

                </tr>
              )
            )
          )}

        </tbody>
      </table>
    </div>

    {/* PAGINATION */}
    <div
      className="flex flex-col gap-3
                 border-t border-gray-300
                 px-6 py-4
                 sm:flex-row
                 sm:items-center
                 sm:justify-between"
    >
      <p className="text-sm text-gray-600">
        Page{" "}
        <span className="font-semibold text-gray-900">
          {pageNumber}
        </span>{" "}
        of{" "}
        <span className="font-semibold text-gray-900">
          {totalPages || 1}
        </span>
      </p>

      <div className="flex gap-2">

        <button
          type="button"
          disabled={
            pageNumber === 1 ||
            isFetching
          }
          onClick={() =>
            setPageNumber(
              (prev) => prev - 1
            )
          }
          className="rounded-lg
                     border border-gray-400
                     bg-white
                     px-4 py-2
                     text-sm
                     font-medium
                     text-gray-700
                     transition
                     hover:bg-gray-100
                     disabled:cursor-not-allowed
                     disabled:opacity-50"
        >
          Previous
        </button>

        <button
          type="button"
          disabled={
            pageNumber >=
              totalPages ||
            isFetching
          }
          onClick={() =>
            setPageNumber(
              (prev) => prev + 1
            )
          }
          className="rounded-lg
                     border border-gray-400
                     bg-white
                     px-4 py-2
                     text-sm
                     font-medium
                     text-gray-700
                     transition
                     hover:bg-gray-100
                     disabled:cursor-not-allowed
                     disabled:opacity-50"
        >
          Next
        </button>

      </div>
    </div>

  </div>

  {/* ========================================
      DELETE CONFIRMATION MODAL
  ======================================== */}
  {deletePropertyId !== null && (
    <div
      className="fixed inset-0 z-50
                 flex items-center
                 justify-center
                 bg-black/50
                 px-4"
    >

      <div
        className="w-full max-w-md
                   rounded-xl
                   bg-white
                   p-6
                   shadow-2xl"
      >

        {/* MODAL HEADER */}
        <div className="flex items-start justify-between">

          <div className="flex items-center gap-3">

            <div
              className="flex h-10 w-10
                         items-center
                         justify-center
                         rounded-full
                         bg-red-100"
            >
              <FaExclamationTriangle
                className="text-red-600"
                size={18}
              />
            </div>

            <h3 className="text-lg font-semibold text-gray-900">
              Delete Property
            </h3>

          </div>

          {/* CLOSE */}
          <button
            type="button"
            disabled={isDeleting}
            onClick={
              handleCancelDelete
            }
            className="text-gray-500
                       transition
                       hover:text-gray-800
                       disabled:opacity-50"
          >
            <FaTimes size={18} />
          </button>

        </div>

        {/* MESSAGE */}
        <div className="mt-5">
          <p className="text-sm leading-6 text-gray-600">
            Are you sure you want to delete
            this property?
          </p>

          <p className="mt-2 text-sm font-medium text-red-600">
            This action cannot be undone.
          </p>
        </div>

        {/* BUTTONS */}
        <div
          className="mt-6 flex
                     justify-end
                     gap-3"
        >

          {/* CANCEL */}
          <button
            type="button"
            disabled={isDeleting}
            onClick={
              handleCancelDelete
            }
            className="rounded-lg
                       border border-gray-400
                       bg-white
                       px-4 py-2
                       text-sm
                       font-medium
                       text-gray-700
                       transition
                       hover:bg-gray-100
                       disabled:cursor-not-allowed
                       disabled:opacity-50"
          >
            Cancel
          </button>

          {/* CONFIRM DELETE */}
          <button
            type="button"
            disabled={isDeleting}
            onClick={
              handleConfirmDelete
            }
            className="rounded-lg
                       bg-red-600
                       px-4 py-2
                       text-sm
                       font-medium
                       text-white
                       transition
                       hover:bg-red-700
                       disabled:cursor-not-allowed
                       disabled:opacity-50"
          >
            {isDeleting
              ? "Deleting..."
              : "Delete"}
          </button>

        </div>

      </div>
    </div>
  )}
</>

);
};

export default PropertyTable;```