PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.10
Yatra – Travel Booking & Tour Operator Software v3.0.10
3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 All 83 releases
yatra / resources / js / pages / DifficultyLevels.tsx

DifficultyLevels.tsx in Yatra – Travel Booking & Tour Operator Software 3.0.10, at resources/js/pages/DifficultyLevels.tsx

800 lines 25.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Difficulty Levels Page
3 * Manage trip difficulty levels with ordering
4 */
5
6 import React, { useState, useMemo } from "react";
7 import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
8 import { Plus, ArrowUp, ArrowDown } from "lucide-react";
9 import { __ } from "../lib/i18n";
10 import { usePermissions } from "../hooks/usePermissions";
11 import { useToast } from "../components/ui/toast";
12 import { apiClient } from "../lib/api-client";
13 import { getErrorContext } from "../lib/errors";
14 import { Button } from "../components/ui/button";
15 import { PageHeader } from "../components/common/PageHeader";
16 import { Card, CardContent } from "../components/ui/card";
17 import { Table } from "../components/shared/Table";
18 import {
19 Pagination,
20 SearchFilterToolbar,
21 BulkActionToolbar,
22 } from "../components/shared";
23 import { getDefaultBulkStatusOptions } from "../components/shared/bulkStatusOptions";
24 import { ConfirmationDialog } from "../components/ui/confirmation-dialog";
25 import { Edit, Trash2 } from "lucide-react";
26 import { IconSelector } from "../components/ui/icon-selector";
27 import type { IconPickerValue } from "../components/ui/icon-picker";
28
29 interface DifficultyLevel {
30 id: number;
31 name: string;
32 slug: string;
33 description: string;
34 icon?: IconPickerValue | null;
35 sorting: number;
36 status: string;
37 trip_count?: number;
38 created_at: string;
39 updated_at: string;
40 created_by: number;
41 updated_by: number;
42 created_by_name?: string;
43 updated_by_name?: string;
44 }
45
46 const DifficultyLevels: React.FC = () => {
47 const [searchTerm, setSearchTerm] = useState("");
48 const [statusFilter, setStatusFilter] = useState("all");
49 const [sortBy, setSortBy] = useState("sorting");
50 const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc");
51 const [page, setPage] = useState(1);
52 const [selectedIds, setSelectedIds] = useState<(string | number)[]>([]);
53 const [bulkAction, setBulkAction] = useState("");
54 const [showColumnsDropdown, setShowColumnsDropdown] = useState(false);
55 const [visibleColumns, setVisibleColumns] = useState(() => {
56 const defaultColumns = {
57 sorting: true,
58 name: true,
59 description: true,
60 trips: true,
61 status: true,
62 created_at: false,
63 };
64 const saved = localStorage.getItem("yatra_difficulty_levels_columns");
65 return saved ? { ...defaultColumns, ...JSON.parse(saved) } : defaultColumns;
66 });
67
68 const { can } = usePermissions();
69 const { showToast } = useToast();
70 const queryClient = useQueryClient();
71 const [deleteConfirm, setDeleteConfirm] = useState<{
72 isOpen: boolean;
73 level: DifficultyLevel | null;
74 }>({
75 isOpen: false,
76 level: null,
77 });
78
79 // Define table columns
80 const columns = [
81 {
82 key: "sorting",
83 label: __("Order", "yatra"),
84 sortable: true,
85 width: "100px",
86 visible: visibleColumns.sorting,
87 render: (level: DifficultyLevel) => (
88 <div className="text-sm text-gray-900 dark:text-white">
89 {level.sorting}
90 </div>
91 ),
92 },
93 {
94 key: "name",
95 label: __("Name", "yatra"),
96 sortable: true,
97 visible: visibleColumns.name,
98 render: (level: DifficultyLevel) => (
99 <div className="flex items-center gap-3">
100 {renderIcon(level.icon)}
101 <div>
102 <a
103 href={`${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=trips&tab=difficulty-levels&action=edit&id=${level.id}`}
104 className="font-medium text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 hover:underline transition-colors cursor-pointer"
105 >
106 {level.name}
107 </a>
108 <div className="text-xs text-gray-500 dark:text-gray-400 flex items-center gap-2">
109 <span>{level.slug || "—"}</span>
110 <span className="text-[11px] text-gray-400 dark:text-gray-500">
111 ({__("ID:", "yatra")} {level.id})
112 </span>
113 </div>
114 </div>
115 </div>
116 ),
117 },
118 {
119 key: "description",
120 label: __("Description", "yatra"),
121 visible: visibleColumns.description,
122 render: (level: DifficultyLevel) => (
123 <div className="max-w-xs truncate text-sm text-gray-600 dark:text-gray-400">
124 {level.description || "—"}
125 </div>
126 ),
127 },
128 {
129 key: "trips",
130 label: __("Trips", "yatra"),
131 visible: visibleColumns.trips,
132 render: (level: DifficultyLevel) => (
133 <span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300">
134 {typeof level.trip_count === "number" ? level.trip_count : 0}
135 </span>
136 ),
137 },
138 {
139 key: "status",
140 label: __("Status", "yatra"),
141 sortable: true,
142 visible: visibleColumns.status,
143 render: (level: DifficultyLevel) => getStatusBadge(level.status),
144 },
145 {
146 key: "created_at",
147 label: __("Created", "yatra"),
148 sortable: true,
149 visible: visibleColumns.created_at,
150 render: (level: DifficultyLevel) => (
151 <div className="text-sm text-gray-600 dark:text-gray-400">
152 {formatDate(level.created_at)}
153 </div>
154 ),
155 },
156 ];
157
158 // Define table actions
159 const actions = [
160 {
161 key: "edit",
162 label: __("Edit", "yatra"),
163 icon: <Edit className="w-4 h-4" />,
164 onClick: (level: DifficultyLevel) => handleEdit(level),
165 condition: () => can("yatra_edit_trips"),
166 },
167 {
168 key: "publish",
169 label: __("Make Published", "yatra"),
170 icon: <Edit className="w-4 h-4" />,
171 onClick: (level: DifficultyLevel) =>
172 statusMutation.mutate({ id: level.id, status: "publish" }),
173 condition: (level: DifficultyLevel) =>
174 can("yatra_edit_trips") && level.status !== "publish",
175 },
176 {
177 key: "draft",
178 label: __("Make Draft", "yatra"),
179 icon: <Edit className="w-4 h-4" />,
180 onClick: (level: DifficultyLevel) =>
181 statusMutation.mutate({ id: level.id, status: "draft" }),
182 condition: (level: DifficultyLevel) =>
183 can("yatra_edit_trips") && level.status !== "draft",
184 },
185 {
186 key: "delete",
187 label: __("Delete", "yatra"),
188 icon: <Trash2 className="w-4 h-4" />,
189 onClick: (level: DifficultyLevel) =>
190 setDeleteConfirm({ isOpen: true, level }),
191 variant: "destructive" as const,
192 condition: (level: DifficultyLevel) =>
193 can("yatra_edit_trips") && level.status === "trash",
194 },
195 {
196 key: "trash",
197 label: __("Move to Trash", "yatra"),
198 icon: <Trash2 className="w-4 h-4" />,
199 onClick: (level: DifficultyLevel) =>
200 statusMutation.mutate({ id: level.id, status: "trash" }),
201 variant: "destructive" as const,
202 condition: (level: DifficultyLevel) =>
203 can("yatra_edit_trips") && level.status !== "trash",
204 },
205 ];
206
207 const queryParams = useMemo(() => {
208 const params: Record<string, any> = {
209 page,
210 per_page: 20,
211 orderby: sortBy,
212 order: sortOrder,
213 };
214
215 if (searchTerm) {
216 params.search = searchTerm;
217 }
218 if (statusFilter !== "all") {
219 params.status = statusFilter;
220 }
221 return params;
222 }, [searchTerm, statusFilter, sortBy, sortOrder, page]);
223
224 // Fetch stable status counts from API (independent of filters)
225 const { data: statsData } = useQuery({
226 queryKey: ["difficulty-levels-stats"],
227 queryFn: async () => {
228 try {
229 const response = await apiClient.get("/difficulty-levels/stats");
230 return response;
231 } catch (error: any) {
232 return { all: 0, publish: 0, draft: 0, trash: 0 };
233 }
234 },
235 enabled: can("yatra_view_trips"),
236 });
237
238 const { data, isLoading, error } = useQuery({
239 queryKey: ["difficulty-levels", queryParams],
240 queryFn: async () => {
241 try {
242 const response = await apiClient.get("/difficulty-levels", {
243 params: queryParams,
244 });
245 return response;
246 } catch (error: any) {
247 showToast(
248 error?.message || __("Failed to load difficulty levels", "yatra"),
249 "error",
250 );
251 throw error;
252 }
253 },
254 enabled: can("yatra_view_trips"),
255 });
256
257 const deleteMutation = useMutation({
258 mutationFn: async (id: number) =>
259 apiClient.delete(`/difficulty-levels/${id}`),
260 onSuccess: () => {
261 queryClient.invalidateQueries({ queryKey: ["difficulty-levels"] });
262 queryClient.invalidateQueries({ queryKey: ["difficulty-levels-stats"] });
263 showToast(
264 __("Difficulty level deleted successfully", "yatra"),
265 "success",
266 );
267 },
268 onError: (error: any) => {
269 showToast(
270 error?.message || __("Failed to delete difficulty level", "yatra"),
271 "error",
272 );
273 },
274 });
275
276 const statusMutation = useMutation({
277 mutationFn: async ({ id, status }: { id: number; status: string }) => {
278 const levelResponse = await apiClient.get(`/difficulty-levels/${id}`);
279 const level = levelResponse as DifficultyLevel;
280
281 return apiClient.put(`/difficulty-levels/${id}`, {
282 name: level.name,
283 slug: level.slug,
284 description: level.description,
285 icon: level.icon,
286 sorting: level.sorting,
287 status,
288 });
289 },
290 onSuccess: (_data, variables) => {
291 queryClient.invalidateQueries({ queryKey: ["difficulty-levels"] });
292 queryClient.invalidateQueries({ queryKey: ["difficulty-levels-stats"] });
293 const msgMap: Record<string, string> = {
294 trash: __("Difficulty level moved to trash", "yatra"),
295 draft: __("Difficulty level marked as draft", "yatra"),
296 publish: __("Difficulty level published", "yatra"),
297 };
298 const msg =
299 msgMap[variables.status] ||
300 __("Difficulty level updated successfully", "yatra");
301 showToast(msg, "success");
302 },
303 onError: (error: any, variables) => {
304 const msgMapError: Record<string, string> = {
305 trash: __("Failed to move difficulty level to trash", "yatra"),
306 draft: __("Failed to mark difficulty level as draft", "yatra"),
307 publish: __("Failed to publish difficulty level", "yatra"),
308 };
309 const fallback =
310 msgMapError[variables.status] ||
311 __("Failed to update difficulty level", "yatra");
312 showToast(error?.message || fallback, "error");
313 },
314 });
315
316 const levels = data?.data || [];
317 const total = data?.total || 0;
318 const totalPages = Math.ceil(total / 20);
319 const errorContext = getErrorContext(error);
320
321 // Status counts from stats API (stable across filters)
322 const statusCounts = useMemo(() => {
323 if (statsData) {
324 return {
325 all: statsData.all ?? 0,
326 publish: statsData.publish ?? 0,
327 draft: statsData.draft ?? 0,
328 trash: statsData.trash ?? 0,
329 };
330 }
331
332 return {
333 all: 0,
334 publish: 0,
335 draft: 0,
336 trash: 0,
337 };
338 }, [statsData]);
339
340 const formatDate = (dateString: string) => {
341 if (!dateString) return __("N/A", "yatra");
342 try {
343 const date = new Date(dateString);
344 return date.toLocaleDateString("en-US", {
345 month: "short",
346 day: "numeric",
347 year: "numeric",
348 hour: "2-digit",
349 minute: "2-digit",
350 });
351 } catch {
352 return dateString;
353 }
354 };
355
356 const getStatusBadge = (status: string) => {
357 const map: Record<string, { className: string; label: string }> = {
358 publish: {
359 className:
360 "bg-green-100 text-green-700 dark:bg-green-900/20 dark:text-green-400",
361 label: __("Publish", "yatra"),
362 },
363 draft: {
364 className:
365 "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-400",
366 label: __("Draft", "yatra"),
367 },
368 trash: {
369 className:
370 "bg-red-100 text-red-700 dark:bg-red-900/20 dark:text-red-400",
371 label: __("Trash", "yatra"),
372 },
373 };
374 const info = map[status] || map.draft;
375 return (
376 <span
377 className={`inline-flex items-center px-2 py-1 rounded-md text-xs font-medium ${info.className}`}
378 >
379 {info.label}
380 </span>
381 );
382 };
383
384 const renderIcon = (icon: IconPickerValue | null | undefined) => {
385 if (!icon) {
386 return (
387 <div className="w-8 h-8 rounded bg-gray-100 dark:bg-gray-800 flex items-center justify-center">
388 <span className="text-xs text-gray-400 dark:text-gray-500">—</span>
389 </div>
390 );
391 }
392 if (icon.type === "image") {
393 return (
394 <img
395 src={icon.value}
396 alt=""
397 className="w-8 h-8 rounded object-cover"
398 onError={(e) => {
399 const target = e.target as HTMLImageElement;
400 target.style.display = "none";
401 if (target.parentElement) {
402 target.parentElement.innerHTML =
403 '<div class="w-8 h-8 rounded bg-gray-100 dark:bg-gray-800 flex items-center justify-center"><span class="text-xs text-gray-400 dark:text-gray-500">—</span></div>';
404 }
405 }}
406 />
407 );
408 }
409 return (
410 <div className="w-8 h-8 rounded bg-gray-100 dark:bg-gray-800 flex items-center justify-center">
411 <IconSelector
412 iconName={icon.value}
413 provider={icon.provider ?? "yatra"}
414 className="w-5 h-5 text-gray-600 dark:text-gray-400"
415 />
416 </div>
417 );
418 };
419
420 const handleEdit = (level: DifficultyLevel) => {
421 window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=trips&tab=difficulty-levels&action=edit&id=${level.id}`;
422 };
423
424 const handleCreate = () => {
425 window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=trips&tab=difficulty-levels&action=create`;
426 };
427
428 const handleSort = (field: string) => {
429 if (sortBy === field) {
430 setSortOrder(sortOrder === "asc" ? "desc" : "asc");
431 } else {
432 setSortBy(field);
433 setSortOrder("asc");
434 }
435 };
436
437 const getSortIcon = (field: string) => {
438 if (sortBy !== field) return null;
439 return sortOrder === "asc" ? (
440 <ArrowUp className="ml-1 w-3.5 h-3.5" />
441 ) : (
442 <ArrowDown className="ml-1 w-3.5 h-3.5" />
443 );
444 };
445
446 const handleResetFilters = () => {
447 setSearchTerm("");
448 setStatusFilter("all");
449 setSortBy("sorting");
450 setSortOrder("asc");
451 setPage(1);
452 };
453
454 const hasFilters =
455 !!searchTerm ||
456 statusFilter !== "all" ||
457 sortBy !== "sorting" ||
458 sortOrder !== "asc";
459
460 const toggleColumn = (columnKey: string) => {
461 const newVisibleColumns = {
462 ...visibleColumns,
463 [columnKey]: !visibleColumns[columnKey as keyof typeof visibleColumns],
464 };
465 setVisibleColumns(newVisibleColumns);
466 localStorage.setItem(
467 "yatra_difficulty_levels_columns",
468 JSON.stringify(newVisibleColumns),
469 );
470 };
471
472 const handleBulkApply = () => {
473 if (!bulkAction) {
474 showToast(__("Select a bulk action first.", "yatra"), "warning");
475 return;
476 }
477
478 if (selectedIds.length === 0) {
479 showToast(
480 __("Select at least one difficulty level.", "yatra"),
481 "warning",
482 );
483 return;
484 }
485
486 const performBulk = async () => {
487 try {
488 if (bulkAction === "delete") {
489 await Promise.all(
490 selectedIds.map((id) =>
491 apiClient.delete(`/difficulty-levels/${id}`),
492 ),
493 );
494 showToast(
495 __("Selected difficulty levels deleted successfully", "yatra"),
496 "success",
497 );
498 } else if (
499 bulkAction === "trash" ||
500 bulkAction === "draft" ||
501 bulkAction === "publish"
502 ) {
503 const status =
504 bulkAction === "trash"
505 ? "trash"
506 : bulkAction === "draft"
507 ? "draft"
508 : "publish";
509
510 await Promise.all(
511 selectedIds.map(async (id) => {
512 try {
513 const levelResponse = await apiClient.get(
514 `/difficulty-levels/${id}`,
515 );
516 const level = levelResponse;
517 if (!level || !level.name) {
518 return;
519 }
520
521 await apiClient.put(`/difficulty-levels/${id}`, {
522 name: level.name,
523 slug: level.slug,
524 description: level.description,
525 icon: level.icon,
526 sorting: level.sorting,
527 status,
528 });
529 } catch {
530 // If a single item fails, let the outer catch handle messaging
531 throw new Error("bulk_item_failed");
532 }
533 }),
534 );
535 const msgMap: Record<string, string> = {
536 trash: __("Selected difficulty levels moved to trash", "yatra"),
537 draft: __("Selected difficulty levels marked as draft", "yatra"),
538 publish: __("Selected difficulty levels published", "yatra"),
539 };
540 showToast(msgMap[bulkAction], "success");
541 }
542
543 queryClient.invalidateQueries({ queryKey: ["difficulty-levels"] });
544 queryClient.invalidateQueries({
545 queryKey: ["difficulty-levels-stats"],
546 });
547 setSelectedIds([]);
548 setBulkAction("");
549 } catch (error: any) {
550 const msgMapError: Record<string, string> = {
551 delete: __("Failed to delete selected difficulty levels", "yatra"),
552 trash: __(
553 "Failed to move selected difficulty levels to trash",
554 "yatra",
555 ),
556 draft: __(
557 "Failed to mark selected difficulty levels as draft",
558 "yatra",
559 ),
560 publish: __("Failed to publish selected difficulty levels", "yatra"),
561 };
562 showToast(error?.message || msgMapError[bulkAction], "error");
563 }
564 };
565
566 void performBulk();
567 };
568
569 return (
570 <div className="space-y-3">
571 <ConfirmationDialog
572 isOpen={deleteConfirm.isOpen}
573 onClose={() => setDeleteConfirm({ isOpen: false, level: null })}
574 onConfirm={() => {
575 if (deleteConfirm.level) {
576 deleteMutation.mutate(deleteConfirm.level.id);
577 setDeleteConfirm({ isOpen: false, level: null });
578 }
579 }}
580 title={__("Delete Difficulty Level", "yatra")}
581 message={
582 deleteConfirm.level
583 ? __(
584 'Are you sure you want to delete "{name}"? This action cannot be undone.',
585 "yatra",
586 ).replace("{name}", deleteConfirm.level.name)
587 : __(
588 "Are you sure you want to delete this difficulty level? This action cannot be undone.",
589 "yatra",
590 )
591 }
592 confirmText={__("Delete", "yatra")}
593 cancelText={__("Cancel", "yatra")}
594 variant="danger"
595 isLoading={deleteMutation.isPending}
596 />
597 <PageHeader
598 title={__("Difficulty Levels", "yatra")}
599 description={__(
600 "Manage trip difficulty levels and their ordering",
601 "yatra",
602 )}
603 actionCapability="yatra_edit_trips"
604 actions={
605 <Button onClick={handleCreate}>
606 <Plus className="w-4 h-4 mr-2" />
607 {__("Add New", "yatra")}
608 </Button>
609 }
610 />
611
612 {/* Filters */}
613 <Card>
614 <CardContent className="p-3">
615 <SearchFilterToolbar
616 searchTerm={searchTerm}
617 onSearchChange={setSearchTerm}
618 statusFilter={statusFilter}
619 onStatusChange={(value) => {
620 setStatusFilter(value);
621 setPage(1);
622 }}
623 statusOptions={[
624 { value: "all", label: __("All Status", "yatra") },
625 { value: "publish", label: __("Published", "yatra") },
626 { value: "draft", label: __("Draft", "yatra") },
627 ]}
628 sortBy={sortBy}
629 onSortByChange={(value) => {
630 setSortBy(value);
631 setSortOrder("asc");
632 setPage(1);
633 }}
634 sortOrder={sortOrder}
635 onSortOrderChange={(order) => {
636 setSortOrder(order);
637 setPage(1);
638 }}
639 sortOptions={[
640 { value: "sorting", label: __("Order", "yatra") },
641 { value: "name", label: __("Name", "yatra") },
642 { value: "created_at", label: __("Created", "yatra") },
643 ]}
644 onResetFilters={handleResetFilters}
645 hasFilters={hasFilters}
646 placeholder={__("Search difficulty levels", "yatra")}
647 />
648 </CardContent>
649 </Card>
650
651 {!error && (
652 <BulkActionToolbar
653 selectedIds={selectedIds}
654 bulkAction={bulkAction}
655 setBulkAction={setBulkAction}
656 onApply={handleBulkApply}
657 onClearSelection={() => setSelectedIds([])}
658 statusFilter={statusFilter}
659 setStatusFilter={(value) => {
660 setStatusFilter(value);
661 setPage(1);
662 setSelectedIds([]);
663 setBulkAction("");
664 }}
665 statusOptions={[
666 { key: "all", label: __("All", "yatra"), count: statusCounts.all },
667 {
668 key: "publish",
669 label: __("Published", "yatra"),
670 count: statusCounts.publish,
671 },
672 {
673 key: "draft",
674 label: __("Draft", "yatra"),
675 count: statusCounts.draft,
676 },
677 {
678 key: "trash",
679 label: __("Trash", "yatra"),
680 count: statusCounts.trash,
681 },
682 ]}
683 showColumnsDropdown={showColumnsDropdown}
684 setShowColumnsDropdown={setShowColumnsDropdown}
685 columnOptions={[
686 {
687 key: "sorting",
688 label: __("Order", "yatra"),
689 visible: visibleColumns.sorting,
690 },
691 {
692 key: "name",
693 label: __("Name", "yatra"),
694 visible: visibleColumns.name,
695 },
696 {
697 key: "slug",
698 label: __("Slug", "yatra"),
699 visible: visibleColumns.slug,
700 },
701 {
702 key: "description",
703 label: __("Description", "yatra"),
704 visible: visibleColumns.description,
705 },
706 {
707 key: "status",
708 label: __("Status", "yatra"),
709 visible: visibleColumns.status,
710 },
711 {
712 key: "created_at",
713 label: __("Created", "yatra"),
714 visible: visibleColumns.created_at,
715 },
716 ]}
717 onToggleColumn={toggleColumn}
718 bulkMutationPending={false}
719 totalItems={levels.length}
720 bulkActionOptions={getDefaultBulkStatusOptions(statusFilter)}
721 />
722 )}
723
724 {/* Table */}
725 <Card>
726 <CardContent className="p-0">
727 <Table
728 data={levels}
729 columns={columns}
730 actions={actions}
731 isLoading={isLoading}
732 isError={!!error}
733 errorText={__("Failed to load difficulty levels", "yatra")}
734 errorDescription={__(
735 "We couldn’t connect to the difficulty levels service. Please refresh or try again shortly.",
736 "We couldn’t connect to the difficulty levels service. Please refresh or try again shortly.",
737 )}
738 onRetry={() =>
739 queryClient.invalidateQueries({ queryKey: ["difficulty-levels"] })
740 }
741 errorDetails={errorContext.details}
742 errorRequestInfo={errorContext.requestInfo}
743 emptyText={__("No difficulty levels found", "yatra")}
744 emptyDescription={__(
745 "Get started by creating your first difficulty level to categorize trips by their physical challenge level.",
746 "Get started by creating your first difficulty level to categorize trips by their physical challenge level.",
747 )}
748 onCreateClick={handleCreate}
749 onSort={handleSort}
750 getSortIcon={getSortIcon}
751 selectedItemIds={selectedIds}
752 onSelectItem={(id: string | number, checked: boolean) => {
753 if (checked) {
754 setSelectedIds([...selectedIds, id]);
755 } else {
756 setSelectedIds(
757 selectedIds.filter((selectedId) => selectedId !== id),
758 );
759 }
760 }}
761 onSelectAll={(checked: boolean) => {
762 if (checked) {
763 setSelectedIds(
764 levels.map((level: DifficultyLevel) => level.id),
765 );
766 } else {
767 setSelectedIds([]);
768 }
769 }}
770 isAllSelected={
771 levels.length > 0 && selectedIds.length === levels.length
772 }
773 getItemId={(level: DifficultyLevel) => level.id}
774 getItemStatus={(level: DifficultyLevel) => level.status}
775 statusFilter={statusFilter}
776 skeletonRows={5}
777 capability="yatra_edit_trips"
778 />
779 </CardContent>
780 </Card>
781
782 {/* Pagination */}
783 {total > 0 && (
784 <div className="mt-4">
785 <Pagination
786 currentPage={page}
787 totalPages={totalPages}
788 totalItems={total}
789 itemsPerPage={10}
790 onPageChange={(newPage) => setPage(newPage)}
791 itemName={__("difficulty levels", "yatra")}
792 />
793 </div>
794 )}
795 </div>
796 );
797 };
798
799 export default DifficultyLevels;
800