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,351 @@
import { Button } from "@/shared/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/ui/card";
import { Checkbox } from "@/shared/ui/checkbox";
import {
Dialog,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger,
} from "@/shared/ui/dialog";
import { Input } from "@/shared/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/shared/ui/select";
import { Edit2, Trash2 } from "lucide-react";
import { useEffect, useState } from "react";
import ReactQuill from "react-quill-new";
import "react-quill-new/dist/quill.snow.css";
type Offer = {
id: string;
title: string;
audience: "Jismoniy shaxslar" | "Yuridik shaxslar";
content: string;
active: boolean;
createdAt: string;
};
const FAKE_DATA: Offer[] = [
{
id: "of-1",
title: "Ommaviy oferta - Standart shartlar",
audience: "Jismoniy shaxslar",
content:
"Bu hujjat kompaniya va xizmatlardan foydalanish bo'yicha umumiy shartlarni o'z ichiga oladi.",
active: true,
createdAt: new Date().toISOString(),
},
{
id: "of-2",
title: "Yuridik shaxslar uchun oferta",
audience: "Yuridik shaxslar",
content: "Yuridik shaxslar uchun maxsus shartlar va kafolatlar.",
active: false,
createdAt: new Date(Date.now() - 86400000).toISOString(),
},
];
const STORAGE_KEY = "ommaviy_oferta_v1";
export default function OmmaviyOfertaCRUD() {
const [items, setItems] = useState<Offer[]>([]);
const [query, setQuery] = useState("");
const [editing, setEditing] = useState<Offer | null>(null);
const [form, setForm] = useState<Partial<Offer>>({
title: "",
audience: "Jismoniy shaxslar",
content: "",
active: true,
});
const [errors, setErrors] = useState<Record<string, string>>({});
useEffect(() => {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
try {
const parsed = JSON.parse(raw) as Offer[];
setItems(parsed);
} catch {
setItems(FAKE_DATA);
}
} else {
setItems(FAKE_DATA);
}
}, []);
useEffect(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
}, [items]);
function resetForm() {
setForm({
title: "",
audience: "Jismoniy shaxslar",
content: "",
active: true,
});
setErrors({});
setEditing(null);
}
function validate(f: Partial<Offer>) {
const e: Record<string, string> = {};
if (!f.title || f.title.trim().length < 3)
e.title = "Sarlavha kamida 3 ta belgidan iborat bo'lishi kerak";
if (!f.content || f.content.trim().length < 10)
e.content = "Kontent kamida 10 ta belgidan iborat bo'lishi kerak";
return e;
}
function handleCreateOrUpdate() {
const validation = validate(form);
if (Object.keys(validation).length) {
setErrors(validation);
return;
}
if (editing) {
setItems((prev) =>
prev.map((it) =>
it.id === editing.id ? { ...it, ...(form as Offer) } : it,
),
);
resetForm();
} else {
const newItem: Offer = {
id: `of-${Date.now()}`,
title: (form.title || "Untitled").trim(),
audience: (form.audience as Offer["audience"]) || "Barcha",
content: (form.content || "").trim(),
active: form.active ?? true,
createdAt: new Date().toISOString(),
};
setItems((prev) => [newItem, ...prev]);
resetForm();
}
}
function startEdit(item: Offer) {
setEditing(item);
setForm({ ...item });
setErrors({});
window.scrollTo({ top: 0, behavior: "smooth" });
}
function removeItem(id: string) {
setItems((prev) => prev.filter((p) => p.id !== id));
}
function toggleActive(id: string) {
setItems((prev) =>
prev.map((p) => (p.id === id ? { ...p, active: !p.active } : p)),
);
}
const filtered = items.filter((it) => {
const q = query.trim().toLowerCase();
if (!q) return true;
return (
it.title.toLowerCase().includes(q) ||
it.content.toLowerCase().includes(q) ||
it.audience.toLowerCase().includes(q)
);
});
return (
<div className="min-h-screen w-full p-6 bg-gray-900">
<div className="max-w-[90%] mx-auto space-y-6">
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold">Ommaviy oferta</h1>
</div>
<Card className="bg-gray-900">
<CardHeader>
<CardTitle>
{editing ? "Tahrirish" : "Yangi oferta yaratish"}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<label className="text-sm font-medium">Sarlavha</label>
<Input
value={form.title || ""}
onChange={(e) =>
setForm((s) => ({ ...s, title: e.target.value }))
}
placeholder="Ommaviy oferta sarlavhasi"
className="mt-1"
/>
{errors.title && (
<p className="text-destructive text-sm mt-1">{errors.title}</p>
)}
</div>
<div className="h-full w-[100%]">
<label className="text-sm font-medium">Kontent</label>
<div className="mt-1">
<ReactQuill
value={form.content || ""}
onChange={(value) =>
setForm((s) => ({ ...s, content: value }))
}
className="bg-gray-900 h-48"
placeholder="Oferta matnini kiriting..."
/>
</div>
{errors.content && (
<p className="text-destructive text-sm mt-1">
{errors.content}
</p>
)}
</div>
<div className="grid grid-cols-1 gap-4 mt-24">
<div>
<label className="text-sm font-medium">Kimlar uchun</label>
<Select
value={form.audience || "Barcha"}
onValueChange={(value) =>
setForm((s) => ({
...s,
audience: value as Offer["audience"],
}))
}
>
<SelectTrigger className="mt-1 w-full !h-12">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="Barcha">Barcha</SelectItem>
<SelectItem value="Jismoniy shaxslar">
Jismoniy shaxslar uchun
</SelectItem>
<SelectItem value="Yuridik shaxslar">
Yuridik shaxslar uchun
</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-end">
<label className="flex items-center gap-2 text-sm cursor-pointer">
<Checkbox
checked={!!form.active}
onCheckedChange={(checked) =>
setForm((s) => ({ ...s, active: checked ? true : false }))
}
/>
<span>Faol</span>
</label>
</div>
</div>
<div className="flex gap-2 pt-4">
<Button
onClick={handleCreateOrUpdate}
className="bg-blue-600 hover:bg-blue-700"
>
{editing ? "Saqlash" : "Yaratish"}
</Button>
<Button variant="outline" onClick={resetForm}>
Bekor qilish
</Button>
</div>
</CardContent>
</Card>
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<Input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Qidirish sarlavha, kontent yoki auditoriya bo'yicha..."
className="max-w-sm"
/>
<div className="flex gap-4 text-sm text-muted-foreground">
<span>Natija: {filtered.length}</span>
<span>Barcha: {items.length}</span>
</div>
</div>
<div className="space-y-3">
{filtered.length === 0 && (
<Card>
<CardContent className="pt-6">
<p className="text-muted-foreground text-center">
Natija topilmadi.
</p>
</CardContent>
</Card>
)}
{filtered.map((it) => (
<Card key={it.id} className="overflow-hidden">
<CardContent className="pt-6">
<div className="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
<div className="flex-1">
<h3 className="font-semibold text-lg">{it.title}</h3>
<p className="text-sm text-muted-foreground mt-1">
{it.audience} {new Date(it.createdAt).toLocaleString()}
</p>
<p className="mt-3 text-sm line-clamp-3">{it.content}</p>
</div>
<div className="flex gap-2 items-center flex-wrap md:flex-col md:flex-nowrap">
<Button
onClick={() => startEdit(it)}
variant="outline"
size="sm"
>
<Edit2 className="w-4 h-4 mr-1" />
Tahrirlash
</Button>
<Button
onClick={() => toggleActive(it.id)}
variant={it.active ? "default" : "outline"}
size="sm"
className={
it.active ? "bg-green-600 hover:bg-green-700" : ""
}
>
{it.active ? "Faol" : "Faol emas"}
</Button>
<Dialog>
<DialogTrigger asChild>
<Button variant="destructive" size="sm">
<Trash2 className="w-4 h-4 mr-1" />
O'chirish
</Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>O'chirish tasdiqlash</DialogTitle>
<DialogDescription>
Haqiqatan ham bu ofertani o'chirmoqchimisiz? Bu amalni
bekor qilib bo'lmaydi.
</DialogDescription>
<div className="flex gap-3 justify-end pt-4">
<Button>Bekor qilish</Button>
<Button
variant={"destructive"}
onClick={() => removeItem(it.id)}
>
O'chirish
</Button>
</div>
</DialogContent>
</Dialog>
</div>
</div>
</CardContent>
</Card>
))}
</div>
</div>
</div>
);
}