PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.6
Yatra – Travel Booking & Tour Operator Software v3.0.2.6
3.0.16 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 All 84 releases
yatra / resources / js / pages / DifficultyLevels.tsx

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

799 lines 25.2 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 className="w-5 h-5 text-gray-600 dark:text-gray-400"
414 />
415 </div>
416 );
417 };
418
419 const handleEdit = (level: DifficultyLevel) => {
420 window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=trips&tab=difficulty-levels&action=edit&id=${level.id}`;
421 };
422
423 const handleCreate = () => {
424 window.location.href = `${window.yatraAdmin?.siteUrl || ""}/wp-admin/admin.php?page=yatra&subpage=trips&tab=difficulty-levels&action=create`;
425 };
426
427 const handleSort = (field: string) => {
428 if (sortBy === field) {
429 setSortOrder(sortOrder === "asc" ? "desc" : "asc");
430 } else {
431 setSortBy(field);
432 setSortOrder("asc");
433 }
434 };
435
436 const getSortIcon = (field: string) => {
437 if (sortBy !== field) return null;
438 return sortOrder === "asc" ? (
439 <ArrowUp className="ml-1 w-3.5 h-3.5" />
440 ) : (
441 <ArrowDown className="ml-1 w-3.5 h-3.5" />
442 );
443 };
444
445 const handleResetFilters = () => {
446 setSearchTerm("");
447 setStatusFilter("all");
448 setSortBy("sorting");
449 setSortOrder("asc");
450 setPage(1);
451 };
452
453 const hasFilters =
454 !!searchTerm ||
455 statusFilter !== "all" ||
456 sortBy !== "sorting" ||
457 sortOrder !== "asc";
458
459 const toggleColumn = (columnKey: string) => {
460 const newVisibleColumns = {
461 ...visibleColumns,
462 [columnKey]: !visibleColumns[columnKey as keyof typeof visibleColumns],
463 };
464 setVisibleColumns(newVisibleColumns);
465 localStorage.setItem(
466 "yatra_difficulty_levels_columns",
467 JSON.stringify(newVisibleColumns),
468 );
469 };
470
471 const handleBulkApply = () => {
472 if (!bulkAction) {
473 showToast(__("Select a bulk action first.", "yatra"), "warning");
474 return;
475 }
476
477 if (selectedIds.length === 0) {
478 showToast(
479 __("Select at least one difficulty level.", "yatra"),
480 "warning",
481 );
482 return;
483 }
484
485 const performBulk = async () => {
486 try {
487 if (bulkAction === "delete") {
488 await Promise.all(
489 selectedIds.map((id) =>
490 apiClient.delete(`/difficulty-levels/${id}`),
491 ),
492 );
493 showToast(
494 __("Selected difficulty levels deleted successfully", "yatra"),
495 "success",
496 );
497 } else if (
498 bulkAction === "trash" ||
499 bulkAction === "draft" ||
500 bulkAction === "publish"
501 ) {
502 const status =
503 bulkAction === "trash"
504 ? "trash"
505 : bulkAction === "draft"
506 ? "draft"
507 : "publish";
508
509 await Promise.all(
510 selectedIds.map(async (id) => {
511 try {
512 const levelResponse = await apiClient.get(
513 `/difficulty-levels/${id}`,
514 );
515 const level = levelResponse;
516 if (!level || !level.name) {
517 return;
518 }
519
520 await apiClient.put(`/difficulty-levels/${id}`, {
521 name: level.name,
522 slug: level.slug,
523 description: level.description,
524 icon: level.icon,
525 sorting: level.sorting,
526 status,
527 });
528 } catch {
529 // If a single item fails, let the outer catch handle messaging
530 throw new Error("bulk_item_failed");
531 }
532 }),
533 );
534 const msgMap: Record<string, string> = {
535 trash: __("Selected difficulty levels moved to trash", "yatra"),
536 draft: __("Selected difficulty levels marked as draft", "yatra"),
537 publish: __("Selected difficulty levels published", "yatra"),
538 };
539 showToast(msgMap[bulkAction], "success");
540 }
541
542 queryClient.invalidateQueries({ queryKey: ["difficulty-levels"] });
543 queryClient.invalidateQueries({
544 queryKey: ["difficulty-levels-stats"],
545 });
546 setSelectedIds([]);
547 setBulkAction("");
548 } catch (error: any) {
549 const msgMapError: Record<string, string> = {
550 delete: __("Failed to delete selected difficulty levels", "yatra"),
551 trash: __(
552 "Failed to move selected difficulty levels to trash",
553 "yatra",
554 ),
555 draft: __(
556 "Failed to mark selected difficulty levels as draft",
557 "yatra",
558 ),
559 publish: __("Failed to publish selected difficulty levels", "yatra"),
560 };
561 showToast(error?.message || msgMapError[bulkAction], "error");
562 }
563 };
564
565 void performBulk();
566 };
567
568 return (
569 <div className="space-y-3">
570 <ConfirmationDialog
571 isOpen={deleteConfirm.isOpen}
572 onClose={() => setDeleteConfirm({ isOpen: false, level: null })}
573 onConfirm={() => {
574 if (deleteConfirm.level) {
575 deleteMutation.mutate(deleteConfirm.level.id);
576 setDeleteConfirm({ isOpen: false, level: null });
577 }
578 }}
579 title={__("Delete Difficulty Level", "yatra")}
580 message={
581 deleteConfirm.level
582 ? __(
583 'Are you sure you want to delete "{name}"? This action cannot be undone.',
584 "yatra",
585 ).replace("{name}", deleteConfirm.level.name)
586 : __(
587 "Are you sure you want to delete this difficulty level? This action cannot be undone.",
588 "yatra",
589 )
590 }
591 confirmText={__("Delete", "yatra")}
592 cancelText={__("Cancel", "yatra")}
593 variant="danger"
594 isLoading={deleteMutation.isPending}
595 />
596 <PageHeader
597 title={__("Difficulty Levels", "yatra")}
598 description={__(
599 "Manage trip difficulty levels and their ordering",
600 "yatra",
601 )}
602 actionCapability="yatra_edit_trips"
603 actions={
604 <Button onClick={handleCreate}>
605 <Plus className="w-4 h-4 mr-2" />
606 {__("Add New", "yatra")}
607 </Button>
608 }
609 />
610
611 {/* Filters */}
612 <Card>
613 <CardContent className="p-3">
614 <SearchFilterToolbar
615 searchTerm={searchTerm}
616 onSearchChange={setSearchTerm}
617 statusFilter={statusFilter}
618 onStatusChange={(value) => {
619 setStatusFilter(value);
620 setPage(1);
621 }}
622 statusOptions={[
623 { value: "all", label: __("All Status", "yatra") },
624 { value: "publish", label: __("Published", "yatra") },
625 { value: "draft", label: __("Draft", "yatra") },
626 ]}
627 sortBy={sortBy}
628 onSortByChange={(value) => {
629 setSortBy(value);
630 setSortOrder("asc");
631 setPage(1);
632 }}
633 sortOrder={sortOrder}
634 onSortOrderChange={(order) => {
635 setSortOrder(order);
636 setPage(1);
637 }}
638 sortOptions={[
639 { value: "sorting", label: __("Order", "yatra") },
640 { value: "name", label: __("Name", "yatra") },
641 { value: "created_at", label: __("Created", "yatra") },
642 ]}
643 onResetFilters={handleResetFilters}
644 hasFilters={hasFilters}
645 placeholder={__("Search difficulty levels", "yatra")}
646 />
647 </CardContent>
648 </Card>
649
650 {!error && (
651 <BulkActionToolbar
652 selectedIds={selectedIds}
653 bulkAction={bulkAction}
654 setBulkAction={setBulkAction}
655 onApply={handleBulkApply}
656 onClearSelection={() => setSelectedIds([])}
657 statusFilter={statusFilter}
658 setStatusFilter={(value) => {
659 setStatusFilter(value);
660 setPage(1);
661 setSelectedIds([]);
662 setBulkAction("");
663 }}
664 statusOptions={[
665 { key: "all", label: __("All", "yatra"), count: statusCounts.all },
666 {
667 key: "publish",
668 label: __("Published", "yatra"),
669 count: statusCounts.publish,
670 },
671 {
672 key: "draft",
673 label: __("Draft", "yatra"),
674 count: statusCounts.draft,
675 },
676 {
677 key: "trash",
678 label: __("Trash", "yatra"),
679 count: statusCounts.trash,
680 },
681 ]}
682 showColumnsDropdown={showColumnsDropdown}
683 setShowColumnsDropdown={setShowColumnsDropdown}
684 columnOptions={[
685 {
686 key: "sorting",
687 label: __("Order", "yatra"),
688 visible: visibleColumns.sorting,
689 },
690 {
691 key: "name",
692 label: __("Name", "yatra"),
693 visible: visibleColumns.name,
694 },
695 {
696 key: "slug",
697 label: __("Slug", "yatra"),
698 visible: visibleColumns.slug,
699 },
700 {
701 key: "description",
702 label: __("Description", "yatra"),
703 visible: visibleColumns.description,
704 },
705 {
706 key: "status",
707 label: __("Status", "yatra"),
708 visible: visibleColumns.status,
709 },
710 {
711 key: "created_at",
712 label: __("Created", "yatra"),
713 visible: visibleColumns.created_at,
714 },
715 ]}
716 onToggleColumn={toggleColumn}
717 bulkMutationPending={false}
718 totalItems={levels.length}
719 bulkActionOptions={getDefaultBulkStatusOptions(statusFilter)}
720 />
721 )}
722
723 {/* Table */}
724 <Card>
725 <CardContent className="p-0">
726 <Table
727 data={levels}
728 columns={columns}
729 actions={actions}
730 isLoading={isLoading}
731 isError={!!error}
732 errorText={__("Failed to load difficulty levels", "yatra")}
733 errorDescription={__(
734 "We couldn’t connect to the difficulty levels service. Please refresh or try again shortly.",
735 "We couldn’t connect to the difficulty levels service. Please refresh or try again shortly.",
736 )}
737 onRetry={() =>
738 queryClient.invalidateQueries({ queryKey: ["difficulty-levels"] })
739 }
740 errorDetails={errorContext.details}
741 errorRequestInfo={errorContext.requestInfo}
742 emptyText={__("No difficulty levels found", "yatra")}
743 emptyDescription={__(
744 "Get started by creating your first difficulty level to categorize trips by their physical challenge level.",
745 "Get started by creating your first difficulty level to categorize trips by their physical challenge level.",
746 )}
747 onCreateClick={handleCreate}
748 onSort={handleSort}
749 getSortIcon={getSortIcon}
750 selectedItemIds={selectedIds}
751 onSelectItem={(id: string | number, checked: boolean) => {
752 if (checked) {
753 setSelectedIds([...selectedIds, id]);
754 } else {
755 setSelectedIds(
756 selectedIds.filter((selectedId) => selectedId !== id),
757 );
758 }
759 }}
760 onSelectAll={(checked: boolean) => {
761 if (checked) {
762 setSelectedIds(
763 levels.map((level: DifficultyLevel) => level.id),
764 );
765 } else {
766 setSelectedIds([]);
767 }
768 }}
769 isAllSelected={
770 levels.length > 0 && selectedIds.length === levels.length
771 }
772 getItemId={(level: DifficultyLevel) => level.id}
773 getItemStatus={(level: DifficultyLevel) => level.status}
774 statusFilter={statusFilter}
775 skeletonRows={5}
776 capability="yatra_edit_trips"
777 />
778 </CardContent>
779 </Card>
780
781 {/* Pagination */}
782 {total > 0 && (
783 <div className="mt-4">
784 <Pagination
785 currentPage={page}
786 totalPages={totalPages}
787 totalItems={total}
788 itemsPerPage={10}
789 onPageChange={(newPage) => setPage(newPage)}
790 itemName={__("difficulty levels", "yatra")}
791 />
792 </div>
793 )}
794 </div>
795 );
796 };
797
798 export default DifficultyLevels;
799