PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.8
Yatra – Travel Booking & Tour Operator Software v3.0.2.8
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 / components / trip-form / sections / IncludedSection.tsx

IncludedSection.tsx in Yatra – Travel Booking & Tour Operator Software 3.0.2.8, at resources/js/components/trip-form/sections/IncludedSection.tsx

329 lines 13.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Included Section Component
3 * Handles: What's included/excluded at trip level
4 */
5
6 import React from "react";
7 import { CheckSquare, Plus, X, AlertCircle } from "lucide-react";
8 import { TripFormSectionProps } from "../types";
9 import { SectionHeader } from "../shared/SectionHeader";
10 import { Button } from "../../ui/button";
11 import { Input } from "../../ui/input";
12 import { Card, CardContent } from "../../ui/card";
13 import { Badge } from "../../ui/badge";
14 import { __ } from "../../../lib/i18n";
15
16 // Custom Textarea component since it doesn't exist in UI
17 const Textarea: React.FC<{
18 value: string;
19 onChange: (e: any) => void;
20 placeholder?: string;
21 rows?: number;
22 className?: string;
23 }> = ({ value, onChange, placeholder, rows = 3, className = "" }) => (
24 <textarea
25 value={value}
26 onChange={onChange}
27 placeholder={placeholder}
28 rows={rows}
29 className={`w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white ${className}`}
30 />
31 );
32
33 interface TripAmenityItem {
34 title: string;
35 description: string;
36 }
37
38 interface IncludedSectionProps extends TripFormSectionProps {
39 formData: any;
40 }
41
42 export const IncludedSection: React.FC<IncludedSectionProps> = ({
43 formData,
44 handleFieldChange,
45 }) => {
46 const [newIncludedItem, setNewIncludedItem] = React.useState<TripAmenityItem>(
47 {
48 title: "",
49 description: "",
50 },
51 );
52
53 const [newExcludedItem, setNewExcludedItem] = React.useState<TripAmenityItem>(
54 {
55 title: "",
56 description: "",
57 },
58 );
59
60 const included_items = formData.included_items || [];
61 const excluded_items = formData.excluded_items || [];
62
63 const addIncludedItem = () => {
64 if (newIncludedItem.title.trim()) {
65 const updatedItems = [...included_items, { ...newIncludedItem }];
66 handleFieldChange("included_items", updatedItems);
67 setNewIncludedItem({ title: "", description: "" });
68 }
69 };
70
71 const addExcludedItem = () => {
72 if (newExcludedItem.title.trim()) {
73 const updatedItems = [...excluded_items, { ...newExcludedItem }];
74 handleFieldChange("excluded_items", updatedItems);
75 setNewExcludedItem({ title: "", description: "" });
76 }
77 };
78
79 const removeIncludedItem = (index: number) => {
80 const updatedItems = included_items.filter(
81 (_: any, i: number) => i !== index,
82 );
83 handleFieldChange("included_items", updatedItems);
84 };
85
86 const removeExcludedItem = (index: number) => {
87 const updatedItems = excluded_items.filter(
88 (_: any, i: number) => i !== index,
89 );
90 handleFieldChange("excluded_items", updatedItems);
91 };
92
93 return (
94 <div className="space-y-6">
95 <SectionHeader
96 icon={CheckSquare}
97 title="What's Included & Excluded"
98 description="Specify what is included and excluded in your trip pricing"
99 />
100
101 <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
102 {/* Included Items */}
103 <Card>
104 <CardContent className="p-6">
105 <div className="flex items-center justify-between mb-4">
106 <h3 className="text-lg font-semibold text-green-700 dark:text-green-400">
107 {__("What's Included", "yatra")}
108 </h3>
109 <Badge
110 variant="success"
111 className="bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400"
112 >
113 {included_items.length} {__("items", "yatra")}
114 </Badge>
115 </div>
116
117 {/* Add New Included Item */}
118 <div className="space-y-4 mb-6 p-4 bg-gradient-to-r from-green-50 to-emerald-50 dark:from-green-950/20 dark:to-emerald-950/20 rounded-xl border border-green-200 dark:border-green-800 shadow-sm">
119 <div className="space-y-3">
120 <Input
121 placeholder={__(
122 "Item title (e.g., Airport Transfer)",
123 "yatra",
124 )}
125 value={newIncludedItem.title}
126 onChange={(e: any) =>
127 setNewIncludedItem({
128 ...newIncludedItem,
129 title: e.target.value,
130 })
131 }
132 className="border-green-300 dark:border-green-700 focus:ring-green-500 focus:border-green-500"
133 />
134 <Textarea
135 placeholder={__("Detailed description (optional)", "yatra")}
136 value={newIncludedItem.description}
137 onChange={(e: any) =>
138 setNewIncludedItem({
139 ...newIncludedItem,
140 description: e.target.value,
141 })
142 }
143 rows={3}
144 className="border-green-300 dark:border-green-700 focus:ring-green-500 focus:border-green-500 resize-none"
145 />
146 </div>
147 <Button
148 onClick={addIncludedItem}
149 disabled={!newIncludedItem.title.trim()}
150 size="sm"
151 className="w-full bg-green-600 hover:bg-green-700 text-white border-green-600 hover:border-green-700 transition-all duration-200 shadow-sm hover:shadow-md"
152 >
153 <Plus className="w-4 h-4 mr-2" />
154 {__("Add Included Item", "yatra")}
155 </Button>
156 </div>
157
158 {/* Existing Included Items */}
159 <div className="space-y-2">
160 {included_items.length === 0 ? (
161 <div className="text-center py-8 text-gray-500 dark:text-gray-400">
162 <CheckSquare className="w-12 h-12 mx-auto mb-2 opacity-50" />
163 <p>{__("No included items added yet", "yatra")}</p>
164 </div>
165 ) : (
166 included_items.map((item: any, index: number) => (
167 <div
168 key={index}
169 className="group flex items-start justify-between p-4 bg-white dark:bg-gray-800 rounded-xl border border-green-200 dark:border-green-800 shadow-sm hover:shadow-md transition-all duration-200"
170 >
171 <div className="flex-1 pr-3">
172 <h4 className="font-semibold text-gray-900 dark:text-white mb-2 flex items-center gap-2">
173 <div className="w-2 h-2 bg-green-500 rounded-full flex-shrink-0"></div>
174 {item.title}
175 </h4>
176 {item.description && (
177 <p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed bg-gray-50 dark:bg-gray-700/50 p-3 rounded-lg">
178 {item.description}
179 </p>
180 )}
181 </div>
182 <Button
183 variant="ghost"
184 size="sm"
185 onClick={() => removeIncludedItem(index)}
186 className="text-red-500 hover:text-red-700 hover:bg-red-50 dark:text-red-400 dark:hover:text-red-300 dark:hover:bg-red-900/20 opacity-0 group-hover:opacity-100 transition-all duration-200 p-2"
187 >
188 <X className="w-4 h-4" />
189 </Button>
190 </div>
191 ))
192 )}
193 </div>
194 </CardContent>
195 </Card>
196
197 {/* Excluded Items */}
198 <Card>
199 <CardContent className="p-6">
200 <div className="flex items-center justify-between mb-4">
201 <h3 className="text-lg font-semibold text-red-700 dark:text-red-400">
202 {__("What's Excluded", "yatra")}
203 </h3>
204 <Badge
205 variant="error"
206 className="bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-400"
207 >
208 {excluded_items.length} {__("items", "yatra")}
209 </Badge>
210 </div>
211
212 {/* Add New Excluded Item */}
213 <div className="space-y-4 mb-6 p-4 bg-gradient-to-r from-red-50 to-rose-50 dark:from-red-950/20 dark:to-rose-950/20 rounded-xl border border-red-200 dark:border-red-800 shadow-sm">
214 <div className="space-y-3">
215 <Input
216 placeholder={__(
217 "Item title (e.g., International Flights)",
218 "yatra",
219 )}
220 value={newExcludedItem.title}
221 onChange={(e: any) =>
222 setNewExcludedItem({
223 ...newExcludedItem,
224 title: e.target.value,
225 })
226 }
227 className="border-red-300 dark:border-red-700 focus:ring-red-500 focus:border-red-500"
228 />
229 <Textarea
230 placeholder={__("Detailed description (optional)", "yatra")}
231 value={newExcludedItem.description}
232 onChange={(e: any) =>
233 setNewExcludedItem({
234 ...newExcludedItem,
235 description: e.target.value,
236 })
237 }
238 rows={3}
239 className="border-red-300 dark:border-red-700 focus:ring-red-500 focus:border-red-500 resize-none"
240 />
241 </div>
242 <Button
243 onClick={addExcludedItem}
244 disabled={!newExcludedItem.title.trim()}
245 size="sm"
246 className="w-full bg-red-600 hover:bg-red-700 text-white border-red-600 hover:border-red-700 transition-all duration-200 shadow-sm hover:shadow-md"
247 >
248 <Plus className="w-4 h-4 mr-2" />
249 {__("Add Excluded Item", "yatra")}
250 </Button>
251 </div>
252
253 {/* Existing Excluded Items */}
254 <div className="space-y-2">
255 {excluded_items.length === 0 ? (
256 <div className="text-center py-8 text-gray-500 dark:text-gray-400">
257 <X className="w-12 h-12 mx-auto mb-2 opacity-50" />
258 <p>{__("No excluded items added yet", "yatra")}</p>
259 </div>
260 ) : (
261 excluded_items.map((item: any, index: number) => (
262 <div
263 key={index}
264 className="group flex items-start justify-between p-4 bg-white dark:bg-gray-800 rounded-xl border border-red-200 dark:border-red-800 shadow-sm hover:shadow-md transition-all duration-200"
265 >
266 <div className="flex-1 pr-3">
267 <h4 className="font-semibold text-gray-900 dark:text-white mb-2 flex items-center gap-2">
268 <div className="w-2 h-2 bg-red-500 rounded-full flex-shrink-0"></div>
269 {item.title}
270 </h4>
271 {item.description && (
272 <p className="text-sm text-gray-600 dark:text-gray-400 leading-relaxed bg-gray-50 dark:bg-gray-700/50 p-3 rounded-lg">
273 {item.description}
274 </p>
275 )}
276 </div>
277 <Button
278 variant="ghost"
279 size="sm"
280 onClick={() => removeExcludedItem(index)}
281 className="text-red-500 hover:text-red-700 hover:bg-red-50 dark:text-red-400 dark:hover:text-red-300 dark:hover:bg-red-900/20 opacity-0 group-hover:opacity-100 transition-all duration-200 p-2"
282 >
283 <X className="w-4 h-4" />
284 </Button>
285 </div>
286 ))
287 )}
288 </div>
289 </CardContent>
290 </Card>
291 </div>
292
293 {/* Help Tips */}
294 <Card className="border-blue-200 dark:border-blue-800 bg-blue-50 dark:bg-blue-950/20">
295 <CardContent className="p-4">
296 <div className="flex items-start space-x-3">
297 <AlertCircle className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
298 <div className="text-sm text-blue-800 dark:text-blue-200">
299 <p className="font-medium mb-1">
300 {__("Tips for Included/Excluded Items:", "yatra")}
301 </p>
302 <ul className="list-disc list-inside space-y-1 text-blue-700 dark:text-blue-300">
303 <li>
304 {__(
305 "Be specific about what's included (meals, transport, guides, etc.)",
306 "yatra",
307 )}
308 </li>
309 <li>
310 {__(
311 "Clearly mention what's NOT included to avoid confusion",
312 "yatra",
313 )}
314 </li>
315 <li>
316 {__("Include items that affect pricing decisions", "yatra")}
317 </li>
318 <li>
319 {__("Keep descriptions concise but informative", "yatra")}
320 </li>
321 </ul>
322 </div>
323 </div>
324 </CardContent>
325 </Card>
326 </div>
327 );
328 };
329