questinaire page added
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/shared/ui/dialog";
|
||||
import Pagination from "@/shared/ui/pagination";
|
||||
import { DialogDescription } from "@radix-ui/react-dialog";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { AxiosError } from "axios";
|
||||
@@ -23,6 +24,8 @@ const FaqList = () => {
|
||||
const [openDelete, setOpenDelete] = useState<boolean>(false);
|
||||
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
||||
const [editingFaq, setEditingFaq] = useState<FaqItem | null>(null);
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const limit = 20;
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
@@ -31,8 +34,8 @@ const FaqList = () => {
|
||||
isLoading,
|
||||
isFetching,
|
||||
} = useQuery({
|
||||
queryKey: ["faqs"],
|
||||
queryFn: async () => faq_api.getFaqs({ page: 1, page_size: 20 }),
|
||||
queryKey: ["faqs", page],
|
||||
queryFn: async () => faq_api.getFaqs({ page: page, page_size: limit }),
|
||||
select(data) {
|
||||
return data.data;
|
||||
},
|
||||
@@ -104,6 +107,12 @@ const FaqList = () => {
|
||||
setEditingFaq={setEditingFaq}
|
||||
/>
|
||||
|
||||
<Pagination
|
||||
currentPage={page}
|
||||
setCurrentPage={setPage}
|
||||
totalPages={faq?.total_pages ?? 1}
|
||||
/>
|
||||
|
||||
<Dialog open={openDelete} onOpenChange={setOpenDelete}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
|
||||
@@ -108,7 +108,7 @@ const OrderTable = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-auto">
|
||||
<div className="flex-1 overflow-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
|
||||
@@ -115,7 +115,7 @@ const ProductTable = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-auto">
|
||||
<div className="flex-1 overflow-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
|
||||
14
src/features/questionnaire/lib/api.ts
Normal file
14
src/features/questionnaire/lib/api.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { QuesList } from "@/features/questionnaire/lib/type";
|
||||
import httpClient from "@/shared/config/api/httpClient";
|
||||
import { API_URLS } from "@/shared/config/api/URLs";
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
export const questionnaire_api = {
|
||||
async list(params: {
|
||||
page: number;
|
||||
page_size: number;
|
||||
}): Promise<AxiosResponse<QuesList>> {
|
||||
const res = await httpClient.get(API_URLS.QuestionnaireList, { params });
|
||||
return res;
|
||||
},
|
||||
};
|
||||
18
src/features/questionnaire/lib/type.ts
Normal file
18
src/features/questionnaire/lib/type.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export interface QuesList {
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
total_pages: number;
|
||||
has_next: boolean;
|
||||
has_previous: boolean;
|
||||
results: QuesListRes[];
|
||||
}
|
||||
|
||||
export interface QuesListRes {
|
||||
id: string;
|
||||
created_at: string;
|
||||
company_name: string;
|
||||
full_name: string;
|
||||
phone_number: string;
|
||||
file: string;
|
||||
}
|
||||
88
src/features/questionnaire/ui/questionnaireDetail.tsx
Normal file
88
src/features/questionnaire/ui/questionnaireDetail.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import type { QuesListRes } from "@/features/questionnaire/lib/type";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/shared/ui/dialog";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
data: QuesListRes | null;
|
||||
}
|
||||
|
||||
const QuestionnaireDetail = ({ open, onClose, data }: Props) => {
|
||||
if (!data) return null;
|
||||
|
||||
const fileUrl = data.file;
|
||||
const ext = fileUrl.split(".").pop()?.toLowerCase();
|
||||
|
||||
const isImage = ["jpg", "jpeg", "png", "webp"].includes(ext ?? "");
|
||||
const isPdf = ext === "pdf";
|
||||
const isOffice = ["doc", "docx", "xls", "xlsx", "odt", "ods"].includes(
|
||||
ext ?? "",
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>So‘rov tafsilotlari</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-3 text-sm">
|
||||
<p>
|
||||
<b>Kompaniya:</b> {data.company_name}
|
||||
</p>
|
||||
<p>
|
||||
<b>F.I.Sh:</b> {data.full_name}
|
||||
</p>
|
||||
<p>
|
||||
<b>Telefon:</b> {data.phone_number}
|
||||
</p>
|
||||
<p>
|
||||
<b>Yaratilgan:</b> {new Date(data.created_at).toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* FILE PREVIEW */}
|
||||
<div className="mt-4 space-y-2">
|
||||
<b>Ilova:</b>
|
||||
|
||||
{isImage && (
|
||||
<img src={fileUrl} alt="file" className="max-h-96 rounded border" />
|
||||
)}
|
||||
|
||||
{isPdf && (
|
||||
<iframe src={fileUrl} className="w-full h-[500px] border rounded" />
|
||||
)}
|
||||
|
||||
{isOffice && (
|
||||
<div className="flex items-center gap-3">
|
||||
<a
|
||||
href={fileUrl}
|
||||
target="_blank"
|
||||
className="text-blue-600 underline"
|
||||
>
|
||||
Faylni yuklab olish
|
||||
</a>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
(Word / Excel / ODF preview brauzerda ochilmaydi)
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
Yopish
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuestionnaireDetail;
|
||||
52
src/features/questionnaire/ui/questionnaireList.tsx
Normal file
52
src/features/questionnaire/ui/questionnaireList.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { questionnaire_api } from "@/features/questionnaire/lib/api";
|
||||
import type { QuesListRes } from "@/features/questionnaire/lib/type";
|
||||
import QuestionnaireDetail from "@/features/questionnaire/ui/questionnaireDetail";
|
||||
import QuestionnaireTable from "@/features/questionnaire/ui/questionnaireTable";
|
||||
import Pagination from "@/shared/ui/pagination";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useState } from "react";
|
||||
|
||||
const QuestionnaireList = () => {
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const [detail, setDetail] = useState<boolean>(false);
|
||||
const [detailQues, setDetailQues] = useState<QuesListRes | null>(null);
|
||||
|
||||
const { data, isLoading, isError, isFetching } = useQuery({
|
||||
queryKey: ["question_list"],
|
||||
queryFn: () => questionnaire_api.list({ page, page_size: 20 }),
|
||||
select(data) {
|
||||
return data.data;
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full p-10 w-full">
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-4 gap-4">
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
<h1 className="text-2xl font-bold">FAQ</h1>
|
||||
</div>
|
||||
</div>
|
||||
<QuestionnaireTable
|
||||
setDetail={setDetail}
|
||||
isError={isError}
|
||||
setDetailQues={setDetailQues}
|
||||
isLoading={isLoading}
|
||||
ques={data ? data.results : []}
|
||||
isFetching={isFetching}
|
||||
/>
|
||||
<Pagination
|
||||
currentPage={page}
|
||||
setCurrentPage={setPage}
|
||||
totalPages={data?.total_pages ?? 1}
|
||||
/>
|
||||
|
||||
<QuestionnaireDetail
|
||||
open={detail}
|
||||
onClose={() => setDetail(false)}
|
||||
data={detailQues}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuestionnaireList;
|
||||
104
src/features/questionnaire/ui/questionnaireTable.tsx
Normal file
104
src/features/questionnaire/ui/questionnaireTable.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
import type { QuesListRes } from "@/features/questionnaire/lib/type";
|
||||
import formatPhone from "@/shared/lib/formatPhone";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/shared/ui/table";
|
||||
import { Eye, Loader2 } from "lucide-react";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
|
||||
interface Props {
|
||||
isLoading: boolean;
|
||||
isFetching: boolean;
|
||||
isError: boolean;
|
||||
ques: QuesListRes[];
|
||||
setDetail: Dispatch<SetStateAction<boolean>>;
|
||||
setDetailQues: Dispatch<SetStateAction<QuesListRes | null>>;
|
||||
}
|
||||
|
||||
const QuestionnaireTable = ({
|
||||
isFetching,
|
||||
isLoading,
|
||||
isError,
|
||||
setDetail,
|
||||
setDetailQues,
|
||||
ques,
|
||||
}: Props) => {
|
||||
return (
|
||||
<div className="flex-1 overflow-auto relative">
|
||||
{(isLoading || isFetching) && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-white/70 z-10">
|
||||
<Loader2 className="animate-spin w-6 h-6 text-gray-600" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isError && (
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<span className="text-lg font-medium text-red-600">
|
||||
Ma'lumotlarni olishda xatolik yuz berdi
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && !isError && (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>#</TableHead>
|
||||
<TableHead>Kompaniya</TableHead>
|
||||
<TableHead>F.I.Sh</TableHead>
|
||||
<TableHead>Telefon</TableHead>
|
||||
<TableHead className="text-right">Amallar</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
<TableBody>
|
||||
{ques.length > 0 ? (
|
||||
ques.map((item, index) => (
|
||||
<TableRow key={item.id}>
|
||||
<TableCell>{index + 1}</TableCell>
|
||||
|
||||
<TableCell className="font-medium">
|
||||
{item.company_name}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>{item.full_name}</TableCell>
|
||||
|
||||
<TableCell>{formatPhone(item.phone_number)}</TableCell>
|
||||
<TableCell className="flex justify-end">
|
||||
<Button
|
||||
variant={"outline"}
|
||||
onClick={() => {
|
||||
setDetail(true);
|
||||
setDetailQues(item);
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<Eye />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={6}
|
||||
className="text-center py-6 text-gray-500"
|
||||
>
|
||||
Maʼlumot topilmadi
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuestionnaireTable;
|
||||
Reference in New Issue
Block a user