971 lines
29 KiB
JavaScript
971 lines
29 KiB
JavaScript
import { useState, useEffect } from "react";
|
||
import {
|
||
getGroceryLists,
|
||
createGroceryList,
|
||
updateGroceryList,
|
||
deleteGroceryList,
|
||
shareGroceryList,
|
||
getGroceryListShares,
|
||
unshareGroceryList,
|
||
searchUsers,
|
||
togglePinGroceryList,
|
||
} from "../groceryApi";
|
||
|
||
function GroceryLists({ user, onShowToast }) {
|
||
const [lists, setLists] = useState([]);
|
||
const [selectedList, setSelectedList] = useState(null);
|
||
const [loading, setLoading] = useState(true);
|
||
const [editingList, setEditingList] = useState(null);
|
||
const [showShareModal, setShowShareModal] = useState(null);
|
||
const [shares, setShares] = useState([]);
|
||
const [userSearch, setUserSearch] = useState("");
|
||
const [searchResults, setSearchResults] = useState([]);
|
||
const [sharePermission, setSharePermission] = useState(false);
|
||
|
||
// New list form
|
||
const [newListName, setNewListName] = useState("");
|
||
const [showNewListForm, setShowNewListForm] = useState(false);
|
||
|
||
// Edit form
|
||
const [editName, setEditName] = useState("");
|
||
const [editItems, setEditItems] = useState([]);
|
||
const [newItem, setNewItem] = useState("");
|
||
|
||
useEffect(() => {
|
||
loadLists();
|
||
}, []);
|
||
|
||
// Restore selected list from localStorage after lists are loaded
|
||
useEffect(() => {
|
||
if (lists.length > 0) {
|
||
try {
|
||
const savedListId = localStorage.getItem("selectedGroceryListId");
|
||
if (savedListId) {
|
||
const listToSelect = lists.find(list => list.id === parseInt(savedListId));
|
||
if (listToSelect) {
|
||
setSelectedList(listToSelect);
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error("Failed to restore selected list", err);
|
||
}
|
||
}
|
||
}, [lists]);
|
||
|
||
const loadLists = async () => {
|
||
try {
|
||
setLoading(true);
|
||
const data = await getGroceryLists();
|
||
setLists(data);
|
||
} catch (error) {
|
||
onShowToast(error.message, "error");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleCreateList = async (e) => {
|
||
e.preventDefault();
|
||
if (!newListName.trim()) return;
|
||
|
||
try {
|
||
const newList = await createGroceryList({
|
||
name: newListName.trim(),
|
||
items: [],
|
||
});
|
||
setLists([newList, ...lists]);
|
||
setNewListName("");
|
||
setShowNewListForm(false);
|
||
onShowToast("רשימת קניות נוצרה בהצלחה", "success");
|
||
} catch (error) {
|
||
onShowToast(error.message, "error");
|
||
}
|
||
};
|
||
|
||
const handleSelectList = (list) => {
|
||
setSelectedList(list);
|
||
setEditingList(null);
|
||
try {
|
||
localStorage.setItem("selectedGroceryListId", list.id.toString());
|
||
} catch (err) {
|
||
console.error("Failed to save selected list", err);
|
||
}
|
||
};
|
||
|
||
const handleEditList = (list) => {
|
||
setEditingList(list);
|
||
setEditName(list.name);
|
||
setEditItems([...list.items]);
|
||
setNewItem("");
|
||
};
|
||
|
||
const handleAddItem = () => {
|
||
if (!newItem.trim()) return;
|
||
setEditItems([...editItems, newItem.trim()]);
|
||
setNewItem("");
|
||
};
|
||
|
||
const handleRemoveItem = (index) => {
|
||
setEditItems(editItems.filter((_, i) => i !== index));
|
||
};
|
||
|
||
const handleToggleItem = (index) => {
|
||
const updated = [...editItems];
|
||
const item = updated[index];
|
||
if (item.startsWith("✓ ")) {
|
||
updated[index] = item.substring(2);
|
||
} else {
|
||
updated[index] = "✓ " + item;
|
||
}
|
||
setEditItems(updated);
|
||
};
|
||
|
||
const handleToggleItemInView = async (index) => {
|
||
if (!selectedList || !selectedList.can_edit) return;
|
||
|
||
const updated = [...selectedList.items];
|
||
const item = updated[index];
|
||
if (item.startsWith("✓ ")) {
|
||
updated[index] = item.substring(2);
|
||
} else {
|
||
updated[index] = "✓ " + item;
|
||
}
|
||
|
||
try {
|
||
const updatedList = await updateGroceryList(selectedList.id, {
|
||
items: updated,
|
||
});
|
||
|
||
setLists(lists.map((l) => (l.id === updatedList.id ? updatedList : l)));
|
||
setSelectedList(updatedList);
|
||
} catch (error) {
|
||
onShowToast(error.message, "error");
|
||
}
|
||
};
|
||
|
||
const handleSaveList = async () => {
|
||
if (!editName.trim()) {
|
||
onShowToast("שם הרשימה לא יכול להיות ריק", "error");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const updated = await updateGroceryList(editingList.id, {
|
||
name: editName.trim(),
|
||
items: editItems,
|
||
});
|
||
|
||
setLists(lists.map((l) => (l.id === updated.id ? updated : l)));
|
||
if (selectedList?.id === updated.id) {
|
||
setSelectedList(updated);
|
||
}
|
||
setEditingList(null);
|
||
onShowToast("רשימת קניות עודכנה בהצלחה", "success");
|
||
} catch (error) {
|
||
onShowToast(error.message, "error");
|
||
}
|
||
};
|
||
|
||
const handleDeleteList = async (listId) => {
|
||
if (!confirm("האם אתה בטוח שברצונך למחוק רשימת קניות זו?")) return;
|
||
|
||
try {
|
||
await deleteGroceryList(listId);
|
||
setLists(lists.filter((l) => l.id !== listId));
|
||
if (selectedList?.id === listId) {
|
||
setSelectedList(null);
|
||
}
|
||
setEditingList(null);
|
||
onShowToast("רשימת קניות נמחקה בהצלחה", "success");
|
||
} catch (error) {
|
||
onShowToast(error.message, "error");
|
||
}
|
||
};
|
||
|
||
const handleTogglePin = async (list) => {
|
||
try {
|
||
const updated = await togglePinGroceryList(list.id);
|
||
setLists(lists.map((l) => (l.id === updated.id ? updated : l)));
|
||
if (selectedList?.id === updated.id) {
|
||
setSelectedList(updated);
|
||
}
|
||
const message = updated.is_pinned
|
||
? "רשימה הוצמדה לדף הבית"
|
||
: "רשימה הוסרה מדף הבית";
|
||
onShowToast(message, "success");
|
||
} catch (error) {
|
||
onShowToast(error.message, "error");
|
||
}
|
||
};
|
||
|
||
const handleShowShareModal = async (list) => {
|
||
setShowShareModal(list);
|
||
setUserSearch("");
|
||
setSearchResults([]);
|
||
setSharePermission(false);
|
||
|
||
try {
|
||
const sharesData = await getGroceryListShares(list.id);
|
||
setShares(sharesData);
|
||
} catch (error) {
|
||
onShowToast(error.message, "error");
|
||
}
|
||
};
|
||
|
||
const handleSearchUsers = async (query) => {
|
||
setUserSearch(query);
|
||
if (query.trim().length < 2) {
|
||
setSearchResults([]);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const results = await searchUsers(query);
|
||
setSearchResults(results);
|
||
} catch (error) {
|
||
onShowToast(error.message, "error");
|
||
}
|
||
};
|
||
|
||
const handleShareWithUser = async (userId, username) => {
|
||
try {
|
||
const share = await shareGroceryList(showShareModal.id, {
|
||
user_identifier: username,
|
||
can_edit: sharePermission,
|
||
});
|
||
|
||
setShares([...shares, share]);
|
||
setUserSearch("");
|
||
setSearchResults([]);
|
||
setSharePermission(false);
|
||
onShowToast(`רשימה שותפה עם ${share.display_name}`, "success");
|
||
} catch (error) {
|
||
onShowToast(error.message, "error");
|
||
}
|
||
};
|
||
|
||
const handleUnshare = async (userId) => {
|
||
try {
|
||
await unshareGroceryList(showShareModal.id, userId);
|
||
setShares(shares.filter((s) => s.shared_with_user_id !== userId));
|
||
onShowToast("שיתוף הוסר בהצלחה", "success");
|
||
} catch (error) {
|
||
onShowToast(error.message, "error");
|
||
}
|
||
};
|
||
|
||
if (loading) {
|
||
return <div className="loading">טוען רשימות קניות...</div>;
|
||
}
|
||
|
||
return (
|
||
<div className="grocery-lists-container">
|
||
<div className="grocery-lists-header">
|
||
<h2>רשימות הקניות שלי</h2>
|
||
<button
|
||
className="btn primary"
|
||
onClick={() => setShowNewListForm(!showNewListForm)}
|
||
>
|
||
{showNewListForm ? "ביטול" : "+ רשימה חדשה"}
|
||
</button>
|
||
</div>
|
||
|
||
{showNewListForm && (
|
||
<form className="new-list-form" onSubmit={handleCreateList}>
|
||
<input
|
||
type="text"
|
||
placeholder="שם הרשימה..."
|
||
value={newListName}
|
||
onChange={(e) => setNewListName(e.target.value)}
|
||
autoFocus
|
||
/>
|
||
<button type="submit" className="btn primary">
|
||
צור רשימה
|
||
</button>
|
||
</form>
|
||
)}
|
||
|
||
<div className="grocery-lists-layout">
|
||
{/* Lists Sidebar */}
|
||
<div className="lists-sidebar">
|
||
{lists.length === 0 ? (
|
||
<p className="empty-message">אין רשימות קניות עדיין</p>
|
||
) : (
|
||
lists.map((list) => (
|
||
<div key={list.id} className="list-item-wrapper">
|
||
<div
|
||
className={`list-item ${selectedList?.id === list.id ? "active" : ""}`}
|
||
onClick={() => handleSelectList(list)}
|
||
>
|
||
<div className="list-item-content">
|
||
<h4>{list.name}</h4>
|
||
<p className="list-item-meta">
|
||
{list.is_owner ? "שלי" : `של ${list.owner_display_name}`}
|
||
{" · "}
|
||
{list.items.length} פריטים
|
||
</p>
|
||
</div>
|
||
</div>
|
||
{list.is_owner && (
|
||
<button
|
||
className="share-icon-btn"
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
handleShowShareModal(list);
|
||
}}
|
||
title="שתף רשימה"
|
||
>
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||
<circle cx="18" cy="5" r="3"/>
|
||
<circle cx="6" cy="12" r="3"/>
|
||
<circle cx="18" cy="19" r="3"/>
|
||
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/>
|
||
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>
|
||
</svg>
|
||
</button>
|
||
)}
|
||
</div>
|
||
))
|
||
)}
|
||
</div>
|
||
|
||
{/* List Details */}
|
||
<div className="list-details">
|
||
{editingList ? (
|
||
<div className="edit-list-form">
|
||
<div className="form-header">
|
||
<h3>עריכת רשימה</h3>
|
||
<button className="btn ghost" onClick={() => setEditingList(null)}>
|
||
ביטול
|
||
</button>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label>שם הרשימה</label>
|
||
<input
|
||
type="text"
|
||
value={editName}
|
||
onChange={(e) => setEditName(e.target.value)}
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label>פריטים</label>
|
||
<div className="add-item-row">
|
||
<input
|
||
type="text"
|
||
placeholder="הוסף פריט..."
|
||
value={newItem}
|
||
onChange={(e) => setNewItem(e.target.value)}
|
||
onKeyPress={(e) => e.key === "Enter" && (e.preventDefault(), handleAddItem())}
|
||
/>
|
||
<button type="button" className="btn primary" onClick={handleAddItem}>
|
||
הוסף
|
||
</button>
|
||
</div>
|
||
|
||
<ul className="items-list">
|
||
{editItems.map((item, index) => (
|
||
<li key={index} className="item-row">
|
||
<button
|
||
type="button"
|
||
className="btn-icon"
|
||
onClick={() => handleToggleItem(index)}
|
||
>
|
||
{item.startsWith("✓ ") ? "☑" : "☐"}
|
||
</button>
|
||
<span className={item.startsWith("✓ ") ? "checked" : ""}>
|
||
{item.startsWith("✓ ") ? item.substring(2) : item}
|
||
</span>
|
||
<button
|
||
type="button"
|
||
className="btn-icon delete"
|
||
onClick={() => handleRemoveItem(index)}
|
||
>
|
||
✕
|
||
</button>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
|
||
<div className="form-actions">
|
||
<button className="btn primary" onClick={handleSaveList}>
|
||
שמור שינויים
|
||
</button>
|
||
{editingList.is_owner && (
|
||
<>
|
||
<button
|
||
className="btn secondary small"
|
||
onClick={() => {
|
||
setEditingList(null);
|
||
handleShowShareModal(editingList);
|
||
}}
|
||
title="שתף רשימה"
|
||
>
|
||
↗️ שתף
|
||
</button>
|
||
<button
|
||
className="btn danger"
|
||
onClick={() => handleDeleteList(editingList.id)}
|
||
>
|
||
מחק רשימה
|
||
</button>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
) : selectedList ? (
|
||
<div className="view-list">
|
||
<div className="view-header">
|
||
<div>
|
||
<h3>{selectedList.name}</h3>
|
||
<p className="list-meta">
|
||
{selectedList.is_owner
|
||
? "רשימה שלי"
|
||
: `משותפת על ידי ${selectedList.owner_display_name}`}
|
||
</p>
|
||
</div>
|
||
<div className="view-header-actions">
|
||
{selectedList.is_owner && (
|
||
<>
|
||
<button
|
||
className={`btn-icon-action ${selectedList.is_pinned ? "pinned" : ""}`}
|
||
onClick={() => handleTogglePin(selectedList)}
|
||
title={selectedList.is_pinned ? "הסר הצמדה" : "הצמד לדף הבית"}
|
||
>
|
||
📌
|
||
</button>
|
||
<button
|
||
className="btn-icon-action"
|
||
onClick={() => handleShowShareModal(selectedList)}
|
||
title="שתף רשימה"
|
||
>
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||
<circle cx="18" cy="5" r="3"/>
|
||
<circle cx="6" cy="12" r="3"/>
|
||
<circle cx="18" cy="19" r="3"/>
|
||
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/>
|
||
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>
|
||
</svg>
|
||
</button>
|
||
</>
|
||
)}
|
||
{selectedList.can_edit && (
|
||
<button
|
||
className="btn-icon-action"
|
||
onClick={() => handleEditList(selectedList)}
|
||
title="ערוך רשימה"
|
||
>
|
||
✏️
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{selectedList.items.length === 0 ? (
|
||
<p className="empty-message">אין פריטים ברשימה</p>
|
||
) : (
|
||
<ul className="items-list view-mode">
|
||
{selectedList.items.map((item, index) => {
|
||
const isChecked = item.startsWith("✓ ");
|
||
const itemText = isChecked ? item.substring(2) : item;
|
||
return (
|
||
<li key={index} className={`item-row ${isChecked ? "checked" : ""}`}>
|
||
{selectedList.can_edit ? (
|
||
<>
|
||
<button
|
||
type="button"
|
||
className="btn-icon"
|
||
onClick={() => handleToggleItemInView(index)}
|
||
>
|
||
{isChecked ? "☑" : "☐"}
|
||
</button>
|
||
<span className={isChecked ? "checked-text" : ""}>
|
||
{itemText}
|
||
</span>
|
||
</>
|
||
) : (
|
||
<>
|
||
<span className="btn-icon">{isChecked ? "☑" : "☐"}</span>
|
||
<span className={isChecked ? "checked-text" : ""}>
|
||
{itemText}
|
||
</span>
|
||
</>
|
||
)}
|
||
</li>
|
||
);
|
||
})}
|
||
</ul>
|
||
)}
|
||
</div>
|
||
) : (
|
||
<div className="empty-state">
|
||
<p>בחר רשימת קניות כדי להציג את הפרטים</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Share Modal */}
|
||
{showShareModal && (
|
||
<div className="modal-overlay" onClick={() => setShowShareModal(null)}>
|
||
<div className="modal share-modal" onClick={(e) => e.stopPropagation()}>
|
||
<div className="modal-header">
|
||
<h3>שתף רשימה: {showShareModal.name}</h3>
|
||
<button className="btn-close" onClick={() => setShowShareModal(null)}>
|
||
✕
|
||
</button>
|
||
</div>
|
||
|
||
<div className="modal-body">
|
||
<div className="share-search">
|
||
<input
|
||
type="text"
|
||
placeholder="חפש משתמש לפי שם משתמש או שם תצוגה..."
|
||
value={userSearch}
|
||
onChange={(e) => handleSearchUsers(e.target.value)}
|
||
/>
|
||
<label className="checkbox-label">
|
||
<input
|
||
type="checkbox"
|
||
checked={sharePermission}
|
||
onChange={(e) => setSharePermission(e.target.checked)}
|
||
/>
|
||
<span>אפשר עריכה</span>
|
||
</label>
|
||
|
||
{searchResults.length > 0 && (
|
||
<ul className="search-results">
|
||
{searchResults.map((user) => (
|
||
<li
|
||
key={user.id}
|
||
onClick={() => handleShareWithUser(user.id, user.username)}
|
||
>
|
||
<div>
|
||
<strong>{user.display_name}</strong>
|
||
<span className="username">@{user.username}</span>
|
||
</div>
|
||
<button className="btn small">שתף</button>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
</div>
|
||
|
||
<div className="shares-list">
|
||
<h4>משותף עם:</h4>
|
||
{shares.length === 0 ? (
|
||
<p className="empty-message">הרשימה לא משותפת עם אף אחד</p>
|
||
) : (
|
||
<ul>
|
||
{shares.map((share) => (
|
||
<li key={share.id} className="share-item">
|
||
<div>
|
||
<strong>{share.display_name}</strong>
|
||
<span className="username">@{share.username}</span>
|
||
{share.can_edit && <span className="badge">עורך</span>}
|
||
</div>
|
||
<button
|
||
className="btn danger small"
|
||
onClick={() => handleUnshare(share.shared_with_user_id)}
|
||
>
|
||
הסר
|
||
</button>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<style>{`
|
||
.grocery-lists-container {
|
||
padding: 1rem;
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.grocery-lists-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.new-list-form {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
margin-bottom: 1.5rem;
|
||
padding: 1.5rem;
|
||
background: var(--panel-bg);
|
||
border-radius: 12px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.new-list-form input {
|
||
flex: 1;
|
||
}
|
||
|
||
.grocery-lists-layout {
|
||
display: grid;
|
||
grid-template-columns: 280px 1fr;
|
||
gap: 1.5rem;
|
||
}
|
||
|
||
.lists-sidebar {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.list-item-wrapper {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.list-item {
|
||
flex: 1;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 1rem;
|
||
background: var(--panel-bg);
|
||
border-radius: 12px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.share-icon-btn {
|
||
background: var(--card-soft);
|
||
border: 1px solid var(--border-color);
|
||
padding: 0.5rem;
|
||
cursor: pointer;
|
||
border-radius: 8px;
|
||
transition: all 0.2s;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--text-secondary);
|
||
flex-shrink: 0;
|
||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.share-icon-btn:hover {
|
||
background: var(--accent-soft);
|
||
border-color: var(--accent);
|
||
color: var(--accent);
|
||
transform: scale(1.05);
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.list-item:hover {
|
||
background: var(--hover-bg);
|
||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.list-item.active {
|
||
background: var(--primary-color);
|
||
color: white;
|
||
}
|
||
|
||
.list-item-content h4 {
|
||
margin: 0 0 0.25rem 0;
|
||
}
|
||
|
||
.list-item-meta {
|
||
margin: 0;
|
||
font-size: 0.875rem;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.list-details {
|
||
background: var(--panel-bg);
|
||
border-radius: 16px;
|
||
padding: 2rem;
|
||
min-height: 400px;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.edit-list-form .form-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 0.5rem;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.add-item-row {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.add-item-row input {
|
||
flex: 1;
|
||
}
|
||
|
||
.items-list {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
}
|
||
|
||
.item-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
padding: 1rem 1.25rem;
|
||
background: var(--panel-bg);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 12px;
|
||
margin-bottom: 0.75rem;
|
||
transition: all 0.2s;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.item-row:hover {
|
||
background: var(--hover-bg);
|
||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.item-row span {
|
||
flex: 1;
|
||
}
|
||
|
||
.item-row span.checked-text {
|
||
text-decoration: line-through;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.item-row span.checked {
|
||
text-decoration: line-through;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.items-list.view-mode .item-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
padding: 1rem 1.25rem;
|
||
background: var(--panel-bg);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 12px;
|
||
margin-bottom: 0.75rem;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
.items-list.view-mode .item-row:hover {
|
||
background: var(--hover-bg);
|
||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.items-list.view-mode .item-row.checked {
|
||
opacity: 0.7;
|
||
background: var(--card-soft);
|
||
}
|
||
|
||
.form-actions {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
margin-top: 1.5rem;
|
||
}
|
||
|
||
.view-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 1.5rem;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.view-header > div:first-child {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.view-header h3 {
|
||
margin: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.view-header-actions {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.list-meta {
|
||
margin: 0.5rem 0 0 0;
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.empty-state,
|
||
.empty-message {
|
||
text-align: center;
|
||
padding: 2rem;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.share-modal {
|
||
background: var(--panel-bg);
|
||
border-radius: 16px;
|
||
width: 90%;
|
||
max-width: 500px;
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 1.5rem;
|
||
border-bottom: 1px solid var(--border-color);
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
.share-search input {
|
||
width: 100%;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.checkbox-label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
margin-bottom: 1rem;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.search-results {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 1rem 0;
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 8px;
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.search-results li {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 0.75rem;
|
||
cursor: pointer;
|
||
border-bottom: 1px solid var(--border-color);
|
||
}
|
||
|
||
.search-results li:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.search-results li:hover {
|
||
background: var(--hover-bg);
|
||
}
|
||
|
||
.username {
|
||
font-size: 0.875rem;
|
||
opacity: 0.7;
|
||
margin-left: 0.5rem;
|
||
}
|
||
|
||
.shares-list {
|
||
margin-top: 2rem;
|
||
}
|
||
|
||
.shares-list h4 {
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.share-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 0.75rem;
|
||
background: var(--hover-bg);
|
||
border-radius: 8px;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.badge {
|
||
display: inline-block;
|
||
padding: 0.25rem 0.5rem;
|
||
background: var(--primary-color);
|
||
color: white;
|
||
border-radius: 6px;
|
||
font-size: 0.75rem;
|
||
margin-right: 0.5rem;
|
||
}
|
||
|
||
.btn-icon-action {
|
||
background: var(--card-soft);
|
||
border: 1px solid var(--border-color);
|
||
padding: 0.5rem 0.75rem;
|
||
cursor: pointer;
|
||
font-size: 1.25rem;
|
||
border-radius: 8px;
|
||
transition: all 0.2s;
|
||
line-height: 1;
|
||
}
|
||
|
||
.btn-icon-action:hover {
|
||
background: var(--hover-bg);
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.grocery-lists-layout {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.lists-sidebar {
|
||
max-height: 300px;
|
||
overflow-y: auto;
|
||
}
|
||
}
|
||
`}</style>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default GroceryLists;
|