setting-page - staffmembers done- no form-ui , no users setting

This commit is contained in:
2025-05-15 13:11:05 +05:30
parent b03b7efcb4
commit 3799568b2d
5 changed files with 536 additions and 1 deletions

View File

@@ -0,0 +1,99 @@
import React, { useState, useEffect } from "react";
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
import { z } from "zod";
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
interface StaffFormProps {
initialData?: Partial<Staff>;
onSubmit: (data: Omit<Staff, "id" | "createdAt">) => void;
onCancel: () => void;
isLoading?: boolean;
}
export function StaffForm({ initialData, onSubmit, onCancel, isLoading }: StaffFormProps) {
const [name, setName] = useState(initialData?.name || "");
const [email, setEmail] = useState(initialData?.email || "");
const [role, setRole] = useState(initialData?.role || "Staff");
const [phone, setPhone] = useState(initialData?.phone || "");
useEffect(() => {
setName(initialData?.name || "");
setEmail(initialData?.email || "");
setRole(initialData?.role || "Staff");
setPhone(initialData?.phone || "");
}, [initialData]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!name.trim()) {
alert("Name is required");
return;
}
onSubmit({ name, email: email || undefined, role, phone: phone || undefined });
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700">Name *</label>
<input
type="text"
className="mt-1 block w-full border rounded p-2"
value={name}
onChange={(e) => setName(e.target.value)}
required
disabled={isLoading}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Email</label>
<input
type="email"
className="mt-1 block w-full border rounded p-2"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={isLoading}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Role *</label>
<input
type="text"
className="mt-1 block w-full border rounded p-2"
value={role}
onChange={(e) => setRole(e.target.value)}
required
disabled={isLoading}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Phone</label>
<input
type="tel"
className="mt-1 block w-full border rounded p-2"
value={phone}
onChange={(e) => setPhone(e.target.value)}
disabled={isLoading}
/>
</div>
<div className="flex justify-end space-x-2">
<button
type="button"
onClick={onCancel}
className="px-4 py-2 border rounded"
disabled={isLoading}
>
Cancel
</button>
<button
type="submit"
className="px-4 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
disabled={isLoading}
>
{isLoading ? "Saving..." : "Save"}
</button>
</div>
</form>
);
}

View File

@@ -0,0 +1,242 @@
import React, { useState } from "react";
import { z } from "zod";
import { StaffUncheckedCreateInputObjectSchema } from "@repo/db/shared/schemas";
type Staff = z.infer<typeof StaffUncheckedCreateInputObjectSchema>;
const staffCreateSchema = StaffUncheckedCreateInputObjectSchema;
const staffUpdateSchema = (
StaffUncheckedCreateInputObjectSchema as unknown as z.ZodObject<any>
).partial();
interface StaffTableProps {
staff: Staff[];
isLoading?: boolean;
isError?: boolean;
onAdd: () => void;
onEdit: (staff: Staff) => void;
onDelete: (id: number) => void;
onView: (staff: Staff) => void;
}
export function StaffTable({
staff,
onEdit,
onView,
onDelete,
onAdd,
}: StaffTableProps) {
const [currentPage, setCurrentPage] = useState<number>(1);
const staffPerPage = 5;
const indexOfLastStaff = currentPage * staffPerPage;
const indexOfFirstStaff = indexOfLastStaff - staffPerPage;
const currentStaff = staff.slice(indexOfFirstStaff, indexOfLastStaff);
const totalPages = Math.ceil(staff.length / staffPerPage);
const getInitials = (name: string) => {
return name
.split(" ")
.map((n: string) => n[0])
.join("")
.toUpperCase();
};
const getAvatarColor = (id: number) => {
const colors = [
"bg-blue-500",
"bg-teal-500",
"bg-amber-500",
"bg-rose-500",
"bg-indigo-500",
"bg-green-500",
"bg-purple-500",
];
return colors[id % colors.length];
};
const formatDate = (dateString: string) => {
const date = new Date(dateString);
return new Intl.DateTimeFormat("en-US", {
day: "2-digit",
month: "short",
year: "numeric",
}).format(date);
};
return (
<div className="bg-white shadow rounded-lg overflow-hidden">
<div className="flex justify-between items-center p-4 border-b border-gray-200">
<h2 className="text-lg font-semibold text-gray-900">Staff Members</h2>
<button
onClick={onAdd}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
Add New Staff
</button>
</div>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Staff
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Email
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Role
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Phone
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Joined
</th>
<th className="relative px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{currentStaff.length === 0 ? (
<tr>
<td colSpan={6} className="text-center py-8 text-gray-500">
No staff found. Add new staff to get started.
</td>
</tr>
) : (
currentStaff.map((staff: Staff) => {
const avatarId = staff.id ?? 0; // fallback if undefined
const formattedDate = staff.createdAt
? formatDate(
typeof staff.createdAt === "string"
? staff.createdAt
: staff.createdAt.toISOString()
)
: "N/A";
return (
<tr key={avatarId} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap flex items-center">
<div
className={`h-10 w-10 rounded-full flex items-center justify-center text-white font-bold ${getAvatarColor(
avatarId
)}`}
>
{getInitials(staff.name)}
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900">
{staff.name}
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{staff.email || "—"}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm capitalize text-gray-900">
{staff.role}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{staff.phone || "—"}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{formattedDate}
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
<button
onClick={() => staff.id !== undefined && onEdit(staff)}
className="text-blue-600 hover:text-blue-900"
aria-label="Edit Staff"
>
Edit
</button>
<button
onClick={() =>
staff.id !== undefined && onDelete(staff.id)
}
className="text-red-600 hover:text-red-900"
aria-label="Delete Staff"
>
Delete
</button>
</td>
</tr>
);
})
)}
</tbody>
</table>
</div>
{staff.length > staffPerPage && (
<div className="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200">
<div className="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
<p className="text-sm text-gray-700">
Showing{" "}
<span className="font-medium">{indexOfFirstStaff + 1}</span> to{" "}
<span className="font-medium">
{Math.min(indexOfLastStaff, staff.length)}
</span>{" "}
of <span className="font-medium">{staff.length}</span> results
</p>
<nav
className="inline-flex -space-x-px rounded-md shadow-sm"
aria-label="Pagination"
>
<a
href="#"
onClick={(e) => {
e.preventDefault();
if (currentPage > 1) setCurrentPage(currentPage - 1);
}}
className={`relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 ${
currentPage === 1 ? "pointer-events-none opacity-50" : ""
}`}
>
Previous
</a>
{Array.from({ length: totalPages }).map((_, i) => (
<a
key={i}
href="#"
onClick={(e) => {
e.preventDefault();
setCurrentPage(i + 1);
}}
aria-current={currentPage === i + 1 ? "page" : undefined}
className={`relative inline-flex items-center px-4 py-2 border text-sm font-medium ${
currentPage === i + 1
? "z-10 bg-blue-50 border-blue-500 text-blue-600"
: "border-gray-300 text-gray-500 hover:bg-gray-50"
}`}
>
{i + 1}
</a>
))}
<a
href="#"
onClick={(e) => {
e.preventDefault();
if (currentPage < totalPages) setCurrentPage(currentPage + 1);
}}
className={`relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 ${
currentPage === totalPages ? "pointer-events-none opacity-50" : ""
}`}
>
Next
</a>
</nav>
</div>
</div>
)}
</div>
);
}