first commit

This commit is contained in:
Samandar Turgunboyev
2025-10-18 17:14:59 +05:00
parent edf364b389
commit 036a36ce90
92 changed files with 14614 additions and 135 deletions

View File

@@ -0,0 +1,360 @@
import formatPhone from "@/shared/lib/formatPhone";
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from "@/shared/ui/form";
import { Input } from "@/shared/ui/input";
import { Label } from "@/shared/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/shared/ui/select";
import { zodResolver } from "@hookform/resolvers/zod";
import { Edit, Phone, Plus, Trash2, X } from "lucide-react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import z from "zod";
const roles = ["Operator", "Bugalter", "Manager"] as const;
type Employee = {
id: number;
firstname: string;
lastname: string;
phone: string;
role: "Operator" | "Bugalter" | "Manager";
};
const initialEmployees: Employee[] = [
{
id: 1,
firstname: "Alisher",
lastname: "Karimov",
phone: "+998901234567",
role: "Operator",
},
{
id: 2,
firstname: "Nigora",
lastname: "Rahimova",
phone: "+998912345678",
role: "Bugalter",
},
{
id: 3,
firstname: "Nigora",
lastname: "Rahimova",
phone: "+998912345678",
role: "Manager",
},
];
const employeeSchema = z.object({
firstname: z.string().min(2, "Kamida 2 ta belgidan iborat bo'lishi kerak"),
lastname: z.string().min(2, "Kamida 2 ta belgidan iborat bo'lishi kerak"),
phone: z.string().min(9, "Telefon raqam noto'g'ri"),
role: z.enum(roles),
});
type EmployeeFormValues = z.infer<typeof employeeSchema>;
const EmployeesManagement = () => {
const form = useForm<EmployeeFormValues>({
resolver: zodResolver(employeeSchema),
defaultValues: {
firstname: "",
lastname: "",
phone: "+998",
role: "Bugalter",
},
});
const [employees, setEmployees] = useState<Employee[]>(initialEmployees);
const [currentPage, setCurrentPage] = useState(1);
const [showModal, setShowModal] = useState(false);
const [modalMode, setModalMode] = useState("add");
const [selectedEmployee, setSelectedEmployee] = useState<Employee | null>(
null,
);
useEffect(() => {
if (selectedEmployee) {
form.setValue("firstname", selectedEmployee.firstname);
form.setValue("lastname", selectedEmployee.lastname);
form.setValue("phone", selectedEmployee.phone);
form.setValue("role", selectedEmployee.role);
}
}, [selectedEmployee, form]);
const itemsPerPage = 6;
const totalPages = Math.ceil(employees.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const currentEmployees = employees.slice(
startIndex,
startIndex + itemsPerPage,
);
const handleAdd = () => {
setModalMode("add");
setShowModal(true);
};
const handleEdit = (employee: Employee) => {
setSelectedEmployee(employee);
setShowModal(true);
setModalMode("edit");
};
const handleDelete = (id: number) => {
if (window.confirm("Ushbu xodimni o'chirishni xohlaysizmi?")) {
setEmployees(employees.filter((emp) => emp.id !== id));
}
};
const onSubmit = (values: EmployeeFormValues) => {
if (modalMode === "add") {
const newEmp: Employee = { ...values, id: Date.now() };
setEmployees([...employees, newEmp]);
} else if (selectedEmployee) {
setEmployees(
employees.map((emp) =>
emp.id === selectedEmployee.id ? { ...emp, ...values } : emp,
),
);
}
setShowModal(false);
};
return (
<div className="min-h-screen w-full bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 text-gray-100 p-6">
<div className="w-[90%] mx-auto">
<div className="flex justify-between items-center mb-8">
<div>
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent">
Xodimlar
</h1>
<p className="text-gray-400 mt-2">
Jami {employees.length} ta xodim
</p>
</div>
<button
onClick={handleAdd}
className="flex cursor-pointer items-center gap-2 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white px-6 py-3 rounded-lg font-semibold transition-all shadow-lg hover:shadow-xl transform hover:scale-105"
>
<Plus size={20} />
Xodim qo'shish
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
{currentEmployees.map((employee) => (
<div
key={employee.id}
className="bg-gray-800/50 backdrop-blur-sm rounded-xl p-6 border border-gray-700 hover:border-blue-500/50 transition-all shadow-lg hover:shadow-2xl transform hover:-translate-y-1"
>
<div className="flex justify-between items-start mb-4">
<div className="flex items-center gap-4">
<div>
<h3 className="text-xl font-bold text-white">
{employee.firstname} {employee.lastname}
</h3>
<p className="text-blue-400 text-sm">{employee.role}</p>
</div>
</div>
</div>
<div className="space-y-3 mb-4">
<div className="flex items-center gap-2 text-gray-300">
<Phone size={16} className="text-gray-500" />
<span className="text-sm">{formatPhone(employee.phone)}</span>
</div>
</div>
<div className="flex gap-2 pt-4 border-t border-gray-700">
<button
onClick={() => handleEdit(employee)}
className="flex-1 cursor-pointer flex items-center justify-center gap-2 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg transition-all font-medium"
>
<Edit size={16} />
Tahrirlash
</button>
<button
onClick={() => handleDelete(employee.id)}
className="flex-1 cursor-pointer flex items-center justify-center gap-2 bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg transition-all font-medium"
>
<Trash2 size={16} />
O'chirish
</button>
</div>
</div>
))}
</div>
<div className="flex justify-end items-center gap-2">
<button
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
disabled={currentPage === 1}
className="px-4 py-2 bg-gray-800 hover:bg-gray-700 disabled:bg-gray-800/50 disabled:cursor-not-allowed text-white rounded-lg transition-all border border-gray-700"
>
Oldingi
</button>
{[...Array(totalPages)].map((_, i) => (
<button
key={i}
onClick={() => setCurrentPage(i + 1)}
className={`px-4 py-2 rounded-lg transition-all border ${
currentPage === i + 1
? "bg-gradient-to-r from-blue-500 to-purple-600 text-white border-transparent"
: "bg-gray-800 hover:bg-gray-700 text-white border-gray-700"
}`}
>
{i + 1}
</button>
))}
<button
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
disabled={currentPage === totalPages}
className="px-4 py-2 bg-gray-800 hover:bg-gray-700 disabled:bg-gray-800/50 disabled:cursor-not-allowed text-white rounded-lg transition-all border border-gray-700"
>
Keyingi
</button>
</div>
</div>
{showModal && (
<div className="fixed inset-0 bg-black/70 backdrop-blur-sm flex items-center justify-center p-4 z-50">
<div className="bg-gray-800 rounded-2xl p-8 max-w-md w-full border border-gray-700 shadow-2xl">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-white">
{modalMode === "add" ? "Xodim qo'shish" : "Xodimni tahrirlash"}
</h2>
<button
onClick={() => setShowModal(false)}
className="text-gray-400 hover:text-white transition-colors"
>
<X size={24} />
</button>
</div>
<div className="space-y-4">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-4"
>
<FormField
control={form.control}
name="firstname"
render={({ field }) => (
<FormItem>
<Label>First Name</Label>
<FormControl>
<Input
placeholder="First Name"
{...field}
className="h-12"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="lastname"
render={({ field }) => (
<FormItem>
<Label>Last Name</Label>
<FormControl>
<Input
placeholder="Last Name"
{...field}
className="h-12"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="phone"
render={({ field }) => (
<FormItem>
<Label>Phone</Label>
<FormControl>
<Input
placeholder="+998 90 123 45 67"
{...field}
value={formatPhone(field.value)}
className="h-12"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="role"
render={({ field }) => (
<FormItem className="w-full">
<Label>Role</Label>
<Select
value={field.value}
onValueChange={(val) => field.onChange(val)}
>
<SelectTrigger className="w-full !h-12 cursor-pointer">
<SelectValue placeholder="Select role" />
</SelectTrigger>
<SelectContent className="cursor-pointer">
{roles.map((r) => (
<SelectItem
key={r}
value={r}
className="cursor-pointer"
>
{r}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<div className="flex gap-3 pt-4">
<button
type="submit"
className="flex-1 cursor-pointer bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white px-6 py-3 rounded-lg font-semibold transition-all shadow-lg"
>
{modalMode === "add" ? "Qo'shish" : "Saqlash"}
</button>
<button
onClick={() => {
setShowModal(false);
form.reset();
}}
className="flex-1 bg-gray-700 cursor-pointer hover:bg-gray-600 text-white px-6 py-3 rounded-lg font-semibold transition-all"
>
Bekor qilish
</button>
</div>
</form>
</Form>
</div>
</div>
</div>
)}
</div>
);
};
export default EmployeesManagement;