/
melissaaaz
/
MemoryOffice
Обзор
Документация
Войти
/
melissaaaz
/
MemoryOffice
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
front/src/components/PackingList.jsx
329 строк
14 KB
melissaaaz
upload files Frontend
19 дек 2025, 21:55
19 дек 2025, 21:55
bb001d3
Код
Авторство
О чём код?
import React, { useState } from 'react' import { Sun, Umbrella, Camera, Smartphone, Droplets, Battery, Wallet, Footprints, Headphones } from 'lucide-react' const PackingList = () => { // Объект для маппинга имен иконок на компоненты const iconMap = { Sun: Sun, Umbrella: Umbrella, Camera: Camera, Smartphone: Smartphone, Droplets: Droplets, Footprints: Footprints, Battery: Battery, Wallet: Wallet, Headphones: Headphones } const [items, setItems] = useState([ { id: 1, name: 'Солнцезащитные очки', iconName: 'Sun', packed: false, color: 'text-yellow-500' }, { id: 2, name: 'Зонт/дождевик', iconName: 'Umbrella', packed: false, color: 'text-blue-500' }, { id: 3, name: 'Фотоаппарат', iconName: 'Camera', packed: false, color: 'text-purple-500' }, { id: 4, name: 'Бутылка воды', iconName: 'Droplets', packed: false, color: 'text-blue-400' }, { id: 5, name: 'Смартфон', iconName: 'Smartphone', packed: false, color: 'text-gray-700' }, { id: 6, name: 'Удобная обувь', iconName: 'Footprints', packed: false, color: 'text-brown-500' }, { id: 7, name: 'Пауэрбанк', iconName: 'Battery', packed: false, color: 'text-green-500' }, { id: 8, name: 'Кошелек', iconName: 'Wallet', packed: false, color: 'text-amber-600' }, { id: 9, name: 'Наушники', iconName: 'Headphones', packed: false, color: 'text-pink-500' } ]) // Drag and drop состояние const [draggedItem, setDraggedItem] = useState(null) const [bagItems, setBagItems] = useState([]) const [showSuccess, setShowSuccess] = useState(false) // Начало перетаскивания const handleDragStart = (e, item) => { const itemData = { id: item.id, name: item.name, iconName: item.iconName, color: item.color, packed: item.packed } e.dataTransfer.setData('application/json', JSON.stringify(itemData)) e.dataTransfer.setData('text/plain', item.name) setDraggedItem(item) e.currentTarget.classList.add('opacity-50', 'scale-95') } // Окончание перетаскивания const handleDragEnd = (e) => { e.currentTarget.classList.remove('opacity-50', 'scale-95') setDraggedItem(null) } // Разрешаем сбрасывание const handleDragOver = (e) => { e.preventDefault() e.dataTransfer.dropEffect = 'move' } // Обработка сбрасывания в сумку const handleDrop = (e) => { e.preventDefault() try { const data = e.dataTransfer.getData('application/json') if (!data) return const itemData = JSON.parse(data) // Проверяем, есть ли уже эта вещь в сумке const exists = bagItems.some(bagItem => bagItem.id === itemData.id) if (exists) return // Добавляем вещь в сумку const updatedBagItems = [...bagItems, itemData] setBagItems(updatedBagItems) // Обновляем исходный список setItems(prevItems => prevItems.map(item => item.id === itemData.id ? { ...item, packed: true } : item ) ) // Показываем успех, если все вещи собраны if (updatedBagItems.length === items.length) { setShowSuccess(true) setTimeout(() => setShowSuccess(false), 3000) } } catch (error) { console.error('Ошибка обработки перетаскивания:', error) } } // Удалить вещь из сумки const removeFromBag = (itemId) => { setBagItems(bagItems.filter(item => item.id !== itemId)) setItems(items.map(item => item.id === itemId ? { ...item, packed: false } : item )) } // Очистить сумку const clearBag = () => { setBagItems([]) setItems(items.map(item => ({ ...item, packed: false }))) } // Собрать все вещи const packAll = () => { const allItems = items.map(item => ({ id: item.id, name: item.name, iconName: item.iconName, color: item.color, packed: true })) setBagItems(allItems) setItems([]) setShowSuccess(true) setTimeout(() => setShowSuccess(false), 3000) } // Вспомогательная функция для отображения иконки const renderIcon = (iconName, color = 'text-blue-600', size = 'h-8 w-8') => { const IconComponent = iconMap[iconName] if (IconComponent) { return <IconComponent className={`${size} ${color}`} /> } return <div className={`${size} ${color} bg-gray-200 rounded-full`} /> } // Функция для получения оставшихся вещей const remainingItems = items.filter(item => !item.packed) return ( <div className="bg-gradient-to-br from-blue-50 to-indigo-50 rounded-2xl p-6 mb-8 border border-blue-200 shadow-lg"> {/* Заголовок и статистика */} <div className="flex flex-col md:flex-row md:items-center justify-between mb-8 gap-4"> <div> <h2 className="text-2xl md:text-3xl font-bold text-gray-800"> Собираемся в путь </h2> <p className="text-gray-600 mt-1"> Соберите всё необходимое для комфортного путешествия </p> </div> <div className="bg-white rounded-xl p-4 shadow-sm border border-blue-100"> <div className="text-center"> <div className="text-sm text-gray-500 mb-1">Прогресс сбора</div> <div className="text-3xl font-bold text-blue-600"> {bagItems.length}<span className="text-gray-400 text-xl">/{items.length}</span> </div> <div className="w-full bg-gray-200 rounded-full h-2 mt-2"> <div className="bg-gradient-to-r from-blue-500 to-green-500 h-2 rounded-full transition-all duration-300" style={{ width: `${(bagItems.length / items.length) * 100}%` }} ></div> </div> </div> </div> </div> <div className="grid grid-cols-1 lg:grid-cols-2 gap-8"> {/* Левая колонка: Вещи для сбора */} <div> <div className="flex items-center justify-between mb-6"> <h3 className="text-xl font-semibold text-gray-800"> Что взять с собой </h3> <span className="text-sm bg-blue-100 text-blue-800 px-3 py-1 rounded-full"> {remainingItems.length} осталось </span> </div> {remainingItems.length > 0 ? ( <div className="grid grid-cols-2 sm:grid-cols-3 gap-4"> {remainingItems.map(item => ( <div key={item.id} draggable onDragStart={(e) => handleDragStart(e, item)} onDragEnd={handleDragEnd} className="group bg-white border-2 border-gray-200 rounded-xl p-4 flex flex-col items-center justify-center cursor-grab hover:border-blue-300 hover:shadow-lg transition-all duration-200 active:cursor-grabbing active:shadow-md" > <div className="h-16 w-16 flex items-center justify-center mb-3 group-hover:scale-110 transition-transform"> {renderIcon(item.iconName, item.color)} </div> <span className="text-sm font-medium text-gray-800 text-center mb-1"> {item.name} </span> <div className="text-xs text-gray-400 flex items-center mt-1"> <span className="animate-pulse">← перетащи</span> </div> </div> ))} </div> ) : ( <div className="text-center py-10 bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl border border-green-200"> <h4 className="text-xl font-semibold text-green-700 mb-2">Всё собрано!</h4> <p className="text-green-600">Вы готовы к путешествию 🎒</p> </div> )} {/* Подсказка */} <div className="mt-6 text-sm text-gray-500 bg-blue-50 p-3 rounded-lg border border-blue-100"> 💡 Перетаскивайте предметы в сумку справа </div> </div> {/* Правая колонка: Сумка */} <div> <div className="flex items-center justify-between mb-6"> <h3 className="text-xl font-semibold text-gray-800"> Ваша дорожная сумка </h3> <div className="flex space-x-2"> <button onClick={packAll} disabled={remainingItems.length === 0} className="px-4 py-2 bg-gradient-to-r from-blue-500 to-blue-600 text-white text-sm rounded-lg hover:from-blue-600 hover:to-blue-700 transition-all disabled:opacity-50 disabled:cursor-not-allowed" > Собрать всё </button> <button onClick={clearBag} disabled={bagItems.length === 0} className="px-4 py-2 bg-gray-100 text-gray-700 text-sm rounded-lg hover:bg-gray-200 transition-all disabled:opacity-50 disabled:cursor-not-allowed" > Очистить </button> </div> </div> {/* Область для перетаскивания */} <div onDragOver={handleDragOver} onDrop={handleDrop} className={`min-h-[300px] border-3 ${draggedItem ? 'border-blue-500 border-dashed bg-blue-50' : 'border-gray-300 border-dashed bg-white'} rounded-2xl p-6 transition-all duration-200 flex flex-col items-center justify-center`} > {bagItems.length === 0 ? ( <> <p className="text-gray-500 text-lg mb-2"> {draggedItem ? 'Отпустите здесь!' : 'Сумка пуста'} </p> <p className="text-gray-400 text-center"> {draggedItem ? `Добавьте "${draggedItem.name}" в сумку` : 'Перетащите предметы сюда или нажмите "Собрать всё"'} </p> </> ) : ( <> <div className="mb-6"> <h4 className="text-lg font-semibold text-gray-800">Содержимое сумки</h4> <p className="text-gray-500 text-sm"> {bagItems.length} из {items.length} предметов </p> </div> <div className="grid grid-cols-2 sm:grid-cols-3 gap-3 w-full"> {bagItems.map(item => ( <div key={item.id} className="bg-white border border-green-200 rounded-lg p-3 flex flex-col items-center shadow-sm hover:shadow-md transition-shadow" > <div className="h-12 w-12 flex items-center justify-center mb-2"> {renderIcon(item.iconName, item.color, 'h-6 w-6')} </div> <span className="text-xs font-medium text-gray-700 text-center mb-2 truncate w-full"> {item.name} </span> <button onClick={() => removeFromBag(item.id)} className="text-xs text-gray-400 hover:text-red-500 hover:bg-red-50 px-2 py-1 rounded transition-colors" title="Убрать из сумки" > Убрать </button> </div> ))} </div> {draggedItem && ( <div className="mt-6 p-3 bg-blue-50 border border-blue-200 rounded-lg"> <p className="text-blue-700 text-sm"> Отпустите чтобы добавить "{draggedItem.name}" </p> </div> )} </> )} </div> </div> </div> {/* Уведомление об успехе */} {showSuccess && ( <div className="fixed bottom-6 right-6 bg-gradient-to-r from-green-500 to-emerald-600 text-white px-6 py-4 rounded-xl shadow-2xl animate-bounce z-50 max-w-sm"> <div className="flex items-center"> <div> <p className="font-semibold">Готово к путешествию! 🎉</p> <p className="text-sm text-green-100 mt-1"> Все {bagItems.length} предметов упакованы </p> </div> </div> <div className="mt-3 pt-3 border-t border-green-400"> <p className="text-xs text-green-100"> Приятного путешествия! Не забудьте проверить документы. </p> </div> </div> )} </div> ) } export default PackingList