Removed tags and added cards
This commit is contained in:
@@ -15,10 +15,6 @@ const Item = sequelize.define('Item', {
|
|||||||
type: DataTypes.TEXT,
|
type: DataTypes.TEXT,
|
||||||
allowNull: false
|
allowNull: false
|
||||||
},
|
},
|
||||||
tags: {
|
|
||||||
type: DataTypes.ARRAY(DataTypes.STRING),
|
|
||||||
defaultValue: []
|
|
||||||
},
|
|
||||||
pickUpAvailable: {
|
pickUpAvailable: {
|
||||||
type: DataTypes.BOOLEAN,
|
type: DataTypes.BOOLEAN,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ const router = express.Router();
|
|||||||
router.get('/', async (req, res) => {
|
router.get('/', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
tags,
|
|
||||||
isPortable,
|
isPortable,
|
||||||
minPrice,
|
minPrice,
|
||||||
maxPrice,
|
maxPrice,
|
||||||
@@ -19,10 +18,6 @@ router.get('/', async (req, res) => {
|
|||||||
|
|
||||||
const where = {};
|
const where = {};
|
||||||
|
|
||||||
if (tags) {
|
|
||||||
const tagsArray = Array.isArray(tags) ? tags : [tags];
|
|
||||||
where.tags = { [Op.overlap]: tagsArray };
|
|
||||||
}
|
|
||||||
if (isPortable !== undefined) where.isPortable = isPortable === 'true';
|
if (isPortable !== undefined) where.isPortable = isPortable === 'true';
|
||||||
if (minPrice || maxPrice) {
|
if (minPrice || maxPrice) {
|
||||||
where.pricePerDay = {};
|
where.pricePerDay = {};
|
||||||
@@ -65,14 +60,9 @@ router.get('/recommendations', authenticateToken, async (req, res) => {
|
|||||||
include: [{ model: Item, as: 'item' }]
|
include: [{ model: Item, as: 'item' }]
|
||||||
});
|
});
|
||||||
|
|
||||||
const rentedTags = userRentals.reduce((tags, rental) => {
|
// For now, just return random available items as recommendations
|
||||||
return [...tags, ...(rental.item.tags || [])];
|
|
||||||
}, []);
|
|
||||||
const uniqueTags = [...new Set(rentedTags)];
|
|
||||||
|
|
||||||
const recommendations = await Item.findAll({
|
const recommendations = await Item.findAll({
|
||||||
where: {
|
where: {
|
||||||
tags: { [Op.overlap]: uniqueTags },
|
|
||||||
availability: true
|
availability: true
|
||||||
},
|
},
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import AvailabilityCalendar from "../components/AvailabilityCalendar";
|
|||||||
interface ItemFormData {
|
interface ItemFormData {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
tags: string[];
|
|
||||||
pickUpAvailable: boolean;
|
pickUpAvailable: boolean;
|
||||||
localDeliveryAvailable: boolean;
|
localDeliveryAvailable: boolean;
|
||||||
localDeliveryRadius?: number;
|
localDeliveryRadius?: number;
|
||||||
@@ -45,7 +44,6 @@ const CreateItem: React.FC = () => {
|
|||||||
const [formData, setFormData] = useState<ItemFormData>({
|
const [formData, setFormData] = useState<ItemFormData>({
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
tags: [],
|
|
||||||
pickUpAvailable: false,
|
pickUpAvailable: false,
|
||||||
localDeliveryAvailable: false,
|
localDeliveryAvailable: false,
|
||||||
localDeliveryRadius: 25,
|
localDeliveryRadius: 25,
|
||||||
@@ -64,7 +62,6 @@ const CreateItem: React.FC = () => {
|
|||||||
needsTraining: false,
|
needsTraining: false,
|
||||||
unavailablePeriods: [],
|
unavailablePeriods: [],
|
||||||
});
|
});
|
||||||
const [tagInput, setTagInput] = useState("");
|
|
||||||
const [imageFiles, setImageFiles] = useState<File[]>([]);
|
const [imageFiles, setImageFiles] = useState<File[]>([]);
|
||||||
const [imagePreviews, setImagePreviews] = useState<string[]>([]);
|
const [imagePreviews, setImagePreviews] = useState<string[]>([]);
|
||||||
const [priceType, setPriceType] = useState<"hour" | "day">("day");
|
const [priceType, setPriceType] = useState<"hour" | "day">("day");
|
||||||
@@ -91,10 +88,10 @@ const CreateItem: React.FC = () => {
|
|||||||
formData.city,
|
formData.city,
|
||||||
formData.state,
|
formData.state,
|
||||||
formData.zipCode,
|
formData.zipCode,
|
||||||
formData.country
|
formData.country,
|
||||||
].filter(part => part && part.trim());
|
].filter((part) => part && part.trim());
|
||||||
|
|
||||||
const location = locationParts.join(', ');
|
const location = locationParts.join(", ");
|
||||||
|
|
||||||
const response = await api.post("/items", {
|
const response = await api.post("/items", {
|
||||||
...formData,
|
...formData,
|
||||||
@@ -129,23 +126,6 @@ const CreateItem: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addTag = () => {
|
|
||||||
if (tagInput.trim() && !formData.tags.includes(tagInput.trim())) {
|
|
||||||
setFormData((prev) => ({
|
|
||||||
...prev,
|
|
||||||
tags: [...prev.tags, tagInput.trim()],
|
|
||||||
}));
|
|
||||||
setTagInput("");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeTag = (tag: string) => {
|
|
||||||
setFormData((prev) => ({
|
|
||||||
...prev,
|
|
||||||
tags: prev.tags.filter((t) => t !== tag),
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const files = Array.from(e.target.files || []);
|
const files = Array.from(e.target.files || []);
|
||||||
|
|
||||||
@@ -186,8 +166,11 @@ const CreateItem: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
|
{/* Images Card */}
|
||||||
|
<div className="card mb-4">
|
||||||
|
<div className="card-body">
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">Images (Max 5)</label>
|
<label className="form-label">Upload Images (Max 5)</label>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
@@ -199,6 +182,7 @@ const CreateItem: React.FC = () => {
|
|||||||
<div className="form-text">
|
<div className="form-text">
|
||||||
Upload up to 5 images of your item
|
Upload up to 5 images of your item
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{imagePreviews.length > 0 && (
|
{imagePreviews.length > 0 && (
|
||||||
<div className="row mt-3">
|
<div className="row mt-3">
|
||||||
@@ -228,7 +212,11 @@ const CreateItem: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Basic Information Card */}
|
||||||
|
<div className="card mb-4">
|
||||||
|
<div className="card-body">
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label htmlFor="name" className="form-label">
|
<label htmlFor="name" className="form-label">
|
||||||
Item Name *
|
Item Name *
|
||||||
@@ -258,49 +246,16 @@ const CreateItem: React.FC = () => {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-3">
|
|
||||||
<label className="form-label">Tags</label>
|
|
||||||
<div className="input-group mb-2">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
value={tagInput}
|
|
||||||
onChange={(e) => setTagInput(e.target.value)}
|
|
||||||
onKeyPress={(e) =>
|
|
||||||
e.key === "Enter" && (e.preventDefault(), addTag())
|
|
||||||
}
|
|
||||||
placeholder="Add a tag"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-outline-secondary"
|
|
||||||
onClick={addTag}
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{formData.tags.map((tag, index) => (
|
|
||||||
<span key={index} className="badge bg-primary me-2 mb-2">
|
|
||||||
{tag}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn-close btn-close-white ms-2"
|
|
||||||
onClick={() => removeTag(tag)}
|
|
||||||
style={{ fontSize: "0.7rem" }}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h6 className="mb-3">Location *</h6>
|
{/* Location Card */}
|
||||||
|
<div className="card mb-4">
|
||||||
|
<div className="card-body">
|
||||||
<div className="row mb-3">
|
<div className="row mb-3">
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label htmlFor="address1" className="form-label">
|
<label htmlFor="address1" className="form-label">
|
||||||
Address Line 1
|
Address Line 1 *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -332,7 +287,7 @@ const CreateItem: React.FC = () => {
|
|||||||
<div className="row mb-3">
|
<div className="row mb-3">
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label htmlFor="city" className="form-label">
|
<label htmlFor="city" className="form-label">
|
||||||
City
|
City *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -346,7 +301,7 @@ const CreateItem: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="col-md-3">
|
<div className="col-md-3">
|
||||||
<label htmlFor="state" className="form-label">
|
<label htmlFor="state" className="form-label">
|
||||||
State
|
State *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -361,7 +316,7 @@ const CreateItem: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="col-md-3">
|
<div className="col-md-3">
|
||||||
<label htmlFor="zipCode" className="form-label">
|
<label htmlFor="zipCode" className="form-label">
|
||||||
ZIP Code
|
ZIP Code *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -378,7 +333,7 @@ const CreateItem: React.FC = () => {
|
|||||||
|
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label htmlFor="country" className="form-label">
|
<label htmlFor="country" className="form-label">
|
||||||
Country
|
Country *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -391,9 +346,15 @@ const CreateItem: React.FC = () => {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mb-3">
|
{/* Delivery & Availability Card */}
|
||||||
<label className="form-label">Availability Type</label>
|
<div className="card mb-4">
|
||||||
|
<div className="card-body">
|
||||||
|
<label className="form-label">
|
||||||
|
How will renters receive this item?
|
||||||
|
</label>
|
||||||
<div className="form-check">
|
<div className="form-check">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -406,8 +367,8 @@ const CreateItem: React.FC = () => {
|
|||||||
<label className="form-check-label" htmlFor="pickUpAvailable">
|
<label className="form-check-label" htmlFor="pickUpAvailable">
|
||||||
Pick-Up
|
Pick-Up
|
||||||
<div className="small text-muted">
|
<div className="small text-muted">
|
||||||
They pick-up the item from your location and they return the
|
They pick-up the item from your location and they return
|
||||||
item to your location
|
the item to your location
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -461,7 +422,10 @@ const CreateItem: React.FC = () => {
|
|||||||
checked={formData.shippingAvailable}
|
checked={formData.shippingAvailable}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
<label className="form-check-label" htmlFor="shippingAvailable">
|
<label
|
||||||
|
className="form-check-label"
|
||||||
|
htmlFor="shippingAvailable"
|
||||||
|
>
|
||||||
Shipping
|
Shipping
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -485,7 +449,11 @@ const CreateItem: React.FC = () => {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pricing Card */}
|
||||||
|
<div className="card mb-4">
|
||||||
|
<div className="card-body">
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<div className="row align-items-center">
|
<div className="row align-items-center">
|
||||||
<div className="col-auto">
|
<div className="col-auto">
|
||||||
@@ -509,9 +477,15 @@ const CreateItem: React.FC = () => {
|
|||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
id={priceType === "hour" ? "pricePerHour" : "pricePerDay"}
|
id={
|
||||||
|
priceType === "hour"
|
||||||
|
? "pricePerHour"
|
||||||
|
: "pricePerDay"
|
||||||
|
}
|
||||||
name={
|
name={
|
||||||
priceType === "hour" ? "pricePerHour" : "pricePerDay"
|
priceType === "hour"
|
||||||
|
? "pricePerHour"
|
||||||
|
: "pricePerDay"
|
||||||
}
|
}
|
||||||
value={
|
value={
|
||||||
priceType === "hour"
|
priceType === "hour"
|
||||||
@@ -543,51 +517,6 @@ const CreateItem: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-4">
|
|
||||||
<h5>Availability Schedule</h5>
|
|
||||||
<p className="text-muted">
|
|
||||||
Select dates when the item is NOT available for rent
|
|
||||||
</p>
|
|
||||||
<AvailabilityCalendar
|
|
||||||
unavailablePeriods={formData.unavailablePeriods || []}
|
|
||||||
onPeriodsChange={(periods) =>
|
|
||||||
setFormData((prev) => ({
|
|
||||||
...prev,
|
|
||||||
unavailablePeriods: periods,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
mode="owner"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-3">
|
|
||||||
<label htmlFor="rules" className="form-label">
|
|
||||||
Rental Rules & Guidelines
|
|
||||||
</label>
|
|
||||||
<div className="form-check mb-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="form-check-input"
|
|
||||||
id="needsTraining"
|
|
||||||
name="needsTraining"
|
|
||||||
checked={formData.needsTraining}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label" htmlFor="needsTraining">
|
|
||||||
Requires in-person training before rental
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<textarea
|
|
||||||
className="form-control"
|
|
||||||
id="rules"
|
|
||||||
name="rules"
|
|
||||||
rows={3}
|
|
||||||
value={formData.rules || ""}
|
|
||||||
onChange={handleChange}
|
|
||||||
placeholder="Any specific rules or guidelines for renting this item"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label htmlFor="replacementCost" className="form-label">
|
<label htmlFor="replacementCost" className="form-label">
|
||||||
Replacement Cost *
|
Replacement Cost *
|
||||||
@@ -610,6 +539,58 @@ const CreateItem: React.FC = () => {
|
|||||||
The cost to replace the item if damaged or lost
|
The cost to replace the item if damaged or lost
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Availability Schedule Card */}
|
||||||
|
<div className="card mb-4">
|
||||||
|
<div className="card-body">
|
||||||
|
<p className="text-muted">
|
||||||
|
Select dates when the item is NOT available for rent
|
||||||
|
</p>
|
||||||
|
<AvailabilityCalendar
|
||||||
|
unavailablePeriods={formData.unavailablePeriods || []}
|
||||||
|
onPeriodsChange={(periods) =>
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
unavailablePeriods: periods,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
mode="owner"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Rules & Guidelines Card */}
|
||||||
|
<div className="card mb-4">
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="form-check mb-3">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="form-check-input"
|
||||||
|
id="needsTraining"
|
||||||
|
name="needsTraining"
|
||||||
|
checked={formData.needsTraining}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
<label className="form-check-label" htmlFor="needsTraining">
|
||||||
|
Requires in-person training before rental
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label htmlFor="rules" className="form-label">
|
||||||
|
Additional Rules or Guidelines
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
className="form-control"
|
||||||
|
id="rules"
|
||||||
|
name="rules"
|
||||||
|
rows={3}
|
||||||
|
value={formData.rules || ""}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Any specific rules or guidelines for renting this item"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="d-grid gap-2">
|
<div className="d-grid gap-2">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from "react";
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from "react-router-dom";
|
||||||
import { Item, Rental } from '../types';
|
import { Item, Rental } from "../types";
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
import { itemAPI, rentalAPI } from '../services/api';
|
import { itemAPI, rentalAPI } from "../services/api";
|
||||||
import AvailabilityCalendar from '../components/AvailabilityCalendar';
|
import AvailabilityCalendar from "../components/AvailabilityCalendar";
|
||||||
import AddressAutocomplete from '../components/AddressAutocomplete';
|
import AddressAutocomplete from "../components/AddressAutocomplete";
|
||||||
|
|
||||||
interface ItemFormData {
|
interface ItemFormData {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
tags: string[];
|
|
||||||
pickUpAvailable: boolean;
|
pickUpAvailable: boolean;
|
||||||
localDeliveryAvailable: boolean;
|
localDeliveryAvailable: boolean;
|
||||||
localDeliveryRadius?: number;
|
localDeliveryRadius?: number;
|
||||||
@@ -41,15 +40,13 @@ const EditItem: React.FC = () => {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [success, setSuccess] = useState(false);
|
const [success, setSuccess] = useState(false);
|
||||||
const [tagInput, setTagInput] = useState("");
|
|
||||||
const [imageFiles, setImageFiles] = useState<File[]>([]);
|
const [imageFiles, setImageFiles] = useState<File[]>([]);
|
||||||
const [imagePreviews, setImagePreviews] = useState<string[]>([]);
|
const [imagePreviews, setImagePreviews] = useState<string[]>([]);
|
||||||
const [priceType, setPriceType] = useState<"hour" | "day">("day");
|
const [priceType, setPriceType] = useState<"hour" | "day">("day");
|
||||||
const [acceptedRentals, setAcceptedRentals] = useState<Rental[]>([]);
|
const [acceptedRentals, setAcceptedRentals] = useState<Rental[]>([]);
|
||||||
const [formData, setFormData] = useState<ItemFormData>({
|
const [formData, setFormData] = useState<ItemFormData>({
|
||||||
name: '',
|
name: "",
|
||||||
description: '',
|
description: "",
|
||||||
tags: [],
|
|
||||||
pickUpAvailable: false,
|
pickUpAvailable: false,
|
||||||
localDeliveryAvailable: false,
|
localDeliveryAvailable: false,
|
||||||
shippingAvailable: false,
|
shippingAvailable: false,
|
||||||
@@ -57,8 +54,8 @@ const EditItem: React.FC = () => {
|
|||||||
pricePerHour: undefined,
|
pricePerHour: undefined,
|
||||||
pricePerDay: undefined,
|
pricePerDay: undefined,
|
||||||
replacementCost: 0,
|
replacementCost: 0,
|
||||||
location: '',
|
location: "",
|
||||||
rules: '',
|
rules: "",
|
||||||
minimumRentalDays: 1,
|
minimumRentalDays: 1,
|
||||||
needsTraining: false,
|
needsTraining: false,
|
||||||
availability: true,
|
availability: true,
|
||||||
@@ -76,22 +73,21 @@ const EditItem: React.FC = () => {
|
|||||||
const item: Item = response.data;
|
const item: Item = response.data;
|
||||||
|
|
||||||
if (item.ownerId !== user?.id) {
|
if (item.ownerId !== user?.id) {
|
||||||
setError('You are not authorized to edit this item');
|
setError("You are not authorized to edit this item");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the price type based on available pricing
|
// Set the price type based on available pricing
|
||||||
if (item.pricePerHour) {
|
if (item.pricePerHour) {
|
||||||
setPriceType('hour');
|
setPriceType("hour");
|
||||||
} else if (item.pricePerDay) {
|
} else if (item.pricePerDay) {
|
||||||
setPriceType('day');
|
setPriceType("day");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert item data to form data format
|
// Convert item data to form data format
|
||||||
setFormData({
|
setFormData({
|
||||||
name: item.name,
|
name: item.name,
|
||||||
description: item.description,
|
description: item.description,
|
||||||
tags: item.tags || [],
|
|
||||||
pickUpAvailable: item.pickUpAvailable || false,
|
pickUpAvailable: item.pickUpAvailable || false,
|
||||||
localDeliveryAvailable: item.localDeliveryAvailable || false,
|
localDeliveryAvailable: item.localDeliveryAvailable || false,
|
||||||
localDeliveryRadius: item.localDeliveryRadius || 25,
|
localDeliveryRadius: item.localDeliveryRadius || 25,
|
||||||
@@ -103,7 +99,7 @@ const EditItem: React.FC = () => {
|
|||||||
location: item.location,
|
location: item.location,
|
||||||
latitude: item.latitude,
|
latitude: item.latitude,
|
||||||
longitude: item.longitude,
|
longitude: item.longitude,
|
||||||
rules: item.rules || '',
|
rules: item.rules || "",
|
||||||
minimumRentalDays: item.minimumRentalDays,
|
minimumRentalDays: item.minimumRentalDays,
|
||||||
needsTraining: item.needsTraining || false,
|
needsTraining: item.needsTraining || false,
|
||||||
availability: item.availability,
|
availability: item.availability,
|
||||||
@@ -115,7 +111,7 @@ const EditItem: React.FC = () => {
|
|||||||
setImagePreviews(item.images);
|
setImagePreviews(item.images);
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err.response?.data?.message || 'Failed to fetch item');
|
setError(err.response?.data?.message || "Failed to fetch item");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -126,13 +122,14 @@ const EditItem: React.FC = () => {
|
|||||||
const response = await rentalAPI.getMyListings();
|
const response = await rentalAPI.getMyListings();
|
||||||
const rentals: Rental[] = response.data;
|
const rentals: Rental[] = response.data;
|
||||||
// Filter for accepted rentals for this specific item
|
// Filter for accepted rentals for this specific item
|
||||||
const itemRentals = rentals.filter(rental =>
|
const itemRentals = rentals.filter(
|
||||||
|
(rental) =>
|
||||||
rental.itemId === id &&
|
rental.itemId === id &&
|
||||||
['confirmed', 'active'].includes(rental.status)
|
["confirmed", "active"].includes(rental.status)
|
||||||
);
|
);
|
||||||
setAcceptedRentals(itemRentals);
|
setAcceptedRentals(itemRentals);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching rentals:', err);
|
console.error("Error fetching rentals:", err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -175,27 +172,10 @@ const EditItem: React.FC = () => {
|
|||||||
navigate(`/items/${id}`);
|
navigate(`/items/${id}`);
|
||||||
}, 1500);
|
}, 1500);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err.response?.data?.message || 'Failed to update item');
|
setError(err.response?.data?.message || "Failed to update item");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addTag = () => {
|
|
||||||
if (tagInput.trim() && !formData.tags.includes(tagInput.trim())) {
|
|
||||||
setFormData((prev) => ({
|
|
||||||
...prev,
|
|
||||||
tags: [...prev.tags, tagInput.trim()],
|
|
||||||
}));
|
|
||||||
setTagInput("");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeTag = (tag: string) => {
|
|
||||||
setFormData((prev) => ({
|
|
||||||
...prev,
|
|
||||||
tags: prev.tags.filter((t) => t !== tag),
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const files = Array.from(e.target.files || []);
|
const files = Array.from(e.target.files || []);
|
||||||
|
|
||||||
@@ -234,7 +214,7 @@ const EditItem: React.FC = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error && error.includes('authorized')) {
|
if (error && error.includes("authorized")) {
|
||||||
return (
|
return (
|
||||||
<div className="container mt-5">
|
<div className="container mt-5">
|
||||||
<div className="alert alert-danger" role="alert">
|
<div className="alert alert-danger" role="alert">
|
||||||
@@ -263,6 +243,9 @@ const EditItem: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
|
{/* Images Card */}
|
||||||
|
<div className="card mb-4">
|
||||||
|
<div className="card-body">
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">Images (Max 5)</label>
|
<label className="form-label">Images (Max 5)</label>
|
||||||
<input
|
<input
|
||||||
@@ -276,6 +259,7 @@ const EditItem: React.FC = () => {
|
|||||||
<div className="form-text">
|
<div className="form-text">
|
||||||
Upload up to 5 images of your item
|
Upload up to 5 images of your item
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{imagePreviews.length > 0 && (
|
{imagePreviews.length > 0 && (
|
||||||
<div className="row mt-3">
|
<div className="row mt-3">
|
||||||
@@ -305,7 +289,11 @@ const EditItem: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Basic Information Card */}
|
||||||
|
<div className="card mb-4">
|
||||||
|
<div className="card-body">
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label htmlFor="name" className="form-label">
|
<label htmlFor="name" className="form-label">
|
||||||
Item Name *
|
Item Name *
|
||||||
@@ -335,66 +323,41 @@ const EditItem: React.FC = () => {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-3">
|
|
||||||
<label className="form-label">Tags</label>
|
|
||||||
<div className="input-group mb-2">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
value={tagInput}
|
|
||||||
onChange={(e) => setTagInput(e.target.value)}
|
|
||||||
onKeyPress={(e) =>
|
|
||||||
e.key === "Enter" && (e.preventDefault(), addTag())
|
|
||||||
}
|
|
||||||
placeholder="Add a tag"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-outline-secondary"
|
|
||||||
onClick={addTag}
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{formData.tags.map((tag, index) => (
|
|
||||||
<span key={index} className="badge bg-primary me-2 mb-2">
|
|
||||||
{tag}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn-close btn-close-white ms-2"
|
|
||||||
onClick={() => removeTag(tag)}
|
|
||||||
style={{ fontSize: "0.7rem" }}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Location Card */}
|
||||||
|
<div className="card mb-4">
|
||||||
|
<div className="card-body">
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label htmlFor="location" className="form-label">
|
<label htmlFor="location" className="form-label">
|
||||||
Location *
|
Address *
|
||||||
</label>
|
</label>
|
||||||
<AddressAutocomplete
|
<AddressAutocomplete
|
||||||
id="location"
|
id="location"
|
||||||
name="location"
|
name="location"
|
||||||
value={formData.location}
|
value={formData.location}
|
||||||
onChange={(value, lat, lon) => {
|
onChange={(value, lat, lon) => {
|
||||||
setFormData(prev => ({
|
setFormData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
location: value,
|
location: value,
|
||||||
latitude: lat,
|
latitude: lat,
|
||||||
longitude: lon
|
longitude: lon,
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
placeholder="Address"
|
placeholder="Enter address"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mb-3">
|
{/* Delivery & Availability Options Card */}
|
||||||
<label className="form-label">Availability Type</label>
|
<div className="card mb-4">
|
||||||
|
<div className="card-body">
|
||||||
|
<label className="form-label">
|
||||||
|
How will renters receive this item?
|
||||||
|
</label>
|
||||||
<div className="form-check">
|
<div className="form-check">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -406,7 +369,10 @@ const EditItem: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
<label className="form-check-label" htmlFor="pickUpAvailable">
|
<label className="form-check-label" htmlFor="pickUpAvailable">
|
||||||
Pick-Up
|
Pick-Up
|
||||||
<div className="small text-muted">They pick-up the item from your location and they return the item to your location</div>
|
<div className="small text-muted">
|
||||||
|
They pick-up the item from your location and they return
|
||||||
|
the item to your location
|
||||||
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-check">
|
<div className="form-check">
|
||||||
@@ -432,18 +398,21 @@ const EditItem: React.FC = () => {
|
|||||||
className="form-control form-control-sm d-inline-block mx-1"
|
className="form-control form-control-sm d-inline-block mx-1"
|
||||||
id="localDeliveryRadius"
|
id="localDeliveryRadius"
|
||||||
name="localDeliveryRadius"
|
name="localDeliveryRadius"
|
||||||
value={formData.localDeliveryRadius || ''}
|
value={formData.localDeliveryRadius || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
placeholder="25"
|
placeholder="25"
|
||||||
min="1"
|
min="1"
|
||||||
max="100"
|
max="100"
|
||||||
style={{ width: '60px' }}
|
style={{ width: "60px" }}
|
||||||
/>
|
/>
|
||||||
miles)
|
miles)
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<div className="small text-muted">You deliver and then pick-up the item when the rental period ends</div>
|
<div className="small text-muted">
|
||||||
|
You deliver and then pick-up the item when the rental
|
||||||
|
period ends
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -456,7 +425,10 @@ const EditItem: React.FC = () => {
|
|||||||
checked={formData.shippingAvailable}
|
checked={formData.shippingAvailable}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
<label className="form-check-label" htmlFor="shippingAvailable">
|
<label
|
||||||
|
className="form-check-label"
|
||||||
|
htmlFor="shippingAvailable"
|
||||||
|
>
|
||||||
Shipping
|
Shipping
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -474,11 +446,17 @@ const EditItem: React.FC = () => {
|
|||||||
htmlFor="inPlaceUseAvailable"
|
htmlFor="inPlaceUseAvailable"
|
||||||
>
|
>
|
||||||
In-Place Use
|
In-Place Use
|
||||||
<div className="small text-muted">They use at your location</div>
|
<div className="small text-muted">
|
||||||
|
They use at your location
|
||||||
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pricing Card */}
|
||||||
|
<div className="card mb-4">
|
||||||
|
<div className="card-body">
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<div className="row align-items-center">
|
<div className="row align-items-center">
|
||||||
<div className="col-auto">
|
<div className="col-auto">
|
||||||
@@ -488,7 +466,9 @@ const EditItem: React.FC = () => {
|
|||||||
<select
|
<select
|
||||||
className="form-select"
|
className="form-select"
|
||||||
value={priceType}
|
value={priceType}
|
||||||
onChange={(e) => setPriceType(e.target.value as "hour" | "day")}
|
onChange={(e) =>
|
||||||
|
setPriceType(e.target.value as "hour" | "day")
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<option value="hour">Hour</option>
|
<option value="hour">Hour</option>
|
||||||
<option value="day">Day</option>
|
<option value="day">Day</option>
|
||||||
@@ -500,9 +480,21 @@ const EditItem: React.FC = () => {
|
|||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
className="form-control"
|
className="form-control"
|
||||||
id={priceType === "hour" ? "pricePerHour" : "pricePerDay"}
|
id={
|
||||||
name={priceType === "hour" ? "pricePerHour" : "pricePerDay"}
|
priceType === "hour"
|
||||||
value={priceType === "hour" ? (formData.pricePerHour || "") : (formData.pricePerDay || "")}
|
? "pricePerHour"
|
||||||
|
: "pricePerDay"
|
||||||
|
}
|
||||||
|
name={
|
||||||
|
priceType === "hour"
|
||||||
|
? "pricePerHour"
|
||||||
|
: "pricePerDay"
|
||||||
|
}
|
||||||
|
value={
|
||||||
|
priceType === "hour"
|
||||||
|
? formData.pricePerHour || ""
|
||||||
|
: formData.pricePerDay || ""
|
||||||
|
}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
step="0.01"
|
step="0.01"
|
||||||
min="0"
|
min="0"
|
||||||
@@ -528,56 +520,6 @@ const EditItem: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-4">
|
|
||||||
<h5>Availability Schedule</h5>
|
|
||||||
<p className="text-muted">Select dates when the item is NOT available for rent. Dates with accepted rentals are shown in purple.</p>
|
|
||||||
<AvailabilityCalendar
|
|
||||||
unavailablePeriods={[
|
|
||||||
...(formData.unavailablePeriods || []),
|
|
||||||
...acceptedRentals.map(rental => ({
|
|
||||||
id: `rental-${rental.id}`,
|
|
||||||
startDate: new Date(rental.startDate),
|
|
||||||
endDate: new Date(rental.endDate),
|
|
||||||
isAcceptedRental: true
|
|
||||||
}))
|
|
||||||
]}
|
|
||||||
onPeriodsChange={(periods) => {
|
|
||||||
// Filter out accepted rental periods when saving
|
|
||||||
const userPeriods = periods.filter(p => !p.isAcceptedRental);
|
|
||||||
setFormData(prev => ({ ...prev, unavailablePeriods: userPeriods }));
|
|
||||||
}}
|
|
||||||
mode="owner"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-3">
|
|
||||||
<label htmlFor="rules" className="form-label">
|
|
||||||
Rental Rules & Guidelines
|
|
||||||
</label>
|
|
||||||
<div className="form-check mb-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="form-check-input"
|
|
||||||
id="needsTraining"
|
|
||||||
name="needsTraining"
|
|
||||||
checked={formData.needsTraining}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
<label className="form-check-label" htmlFor="needsTraining">
|
|
||||||
Requires in-person training before rental
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<textarea
|
|
||||||
className="form-control"
|
|
||||||
id="rules"
|
|
||||||
name="rules"
|
|
||||||
rows={3}
|
|
||||||
value={formData.rules || ""}
|
|
||||||
onChange={handleChange}
|
|
||||||
placeholder="Any specific rules or guidelines for renting this item"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label htmlFor="replacementCost" className="form-label">
|
<label htmlFor="replacementCost" className="form-label">
|
||||||
Replacement Cost *
|
Replacement Cost *
|
||||||
@@ -600,8 +542,40 @@ const EditItem: React.FC = () => {
|
|||||||
The cost to replace the item if damaged or lost
|
The cost to replace the item if damaged or lost
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mb-3 form-check">
|
{/* Availability Schedule Card */}
|
||||||
|
<div className="card mb-4">
|
||||||
|
<div className="card-body">
|
||||||
|
<p className="text-muted">
|
||||||
|
Select dates when the item is NOT available for rent. Dates
|
||||||
|
with accepted rentals are shown in purple.
|
||||||
|
</p>
|
||||||
|
<AvailabilityCalendar
|
||||||
|
unavailablePeriods={[
|
||||||
|
...(formData.unavailablePeriods || []),
|
||||||
|
...acceptedRentals.map((rental) => ({
|
||||||
|
id: `rental-${rental.id}`,
|
||||||
|
startDate: new Date(rental.startDate),
|
||||||
|
endDate: new Date(rental.endDate),
|
||||||
|
isAcceptedRental: true,
|
||||||
|
})),
|
||||||
|
]}
|
||||||
|
onPeriodsChange={(periods) => {
|
||||||
|
// Filter out accepted rental periods when saving
|
||||||
|
const userPeriods = periods.filter(
|
||||||
|
(p) => !p.isAcceptedRental
|
||||||
|
);
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
unavailablePeriods: userPeriods,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
mode="owner"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-3 form-check">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="form-check-input"
|
className="form-check-input"
|
||||||
@@ -614,6 +588,39 @@ const EditItem: React.FC = () => {
|
|||||||
Available for rent
|
Available for rent
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Rules & Guidelines Card */}
|
||||||
|
<div className="card mb-4">
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="form-check mb-3">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="form-check-input"
|
||||||
|
id="needsTraining"
|
||||||
|
name="needsTraining"
|
||||||
|
checked={formData.needsTraining}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
<label className="form-check-label" htmlFor="needsTraining">
|
||||||
|
Requires in-person training before rental
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label htmlFor="rules" className="form-label">
|
||||||
|
Additional Rules or Guidelines
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
className="form-control"
|
||||||
|
id="rules"
|
||||||
|
name="rules"
|
||||||
|
rows={3}
|
||||||
|
value={formData.rules || ""}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Any specific rules or guidelines for renting this item"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="d-grid gap-2">
|
<div className="d-grid gap-2">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -146,11 +146,6 @@ const ItemDetail: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mb-3">
|
|
||||||
{item.tags.map((tag, index) => (
|
|
||||||
<span key={index} className="badge bg-secondary me-2">{tag}</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h5>Description</h5>
|
<h5>Description</h5>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ const ItemList: React.FC = () => {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [filterTag, setFilterTag] = useState('');
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchItems();
|
fetchItems();
|
||||||
@@ -32,18 +31,14 @@ const ItemList: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get unique tags from all items
|
|
||||||
const allTags = Array.from(new Set(items.flatMap(item => item.tags || [])));
|
|
||||||
|
|
||||||
// Filter items based on search term and selected tag
|
// Filter items based on search term
|
||||||
const filteredItems = items.filter(item => {
|
const filteredItems = items.filter(item => {
|
||||||
const matchesSearch = searchTerm === '' ||
|
const matchesSearch = searchTerm === '' ||
|
||||||
item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
item.description.toLowerCase().includes(searchTerm.toLowerCase());
|
item.description.toLowerCase().includes(searchTerm.toLowerCase());
|
||||||
|
|
||||||
const matchesTag = filterTag === '' || (item.tags && item.tags.includes(filterTag));
|
return matchesSearch;
|
||||||
|
|
||||||
return matchesSearch && matchesTag;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -82,18 +77,6 @@ const ItemList: React.FC = () => {
|
|||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-4">
|
|
||||||
<select
|
|
||||||
className="form-select"
|
|
||||||
value={filterTag}
|
|
||||||
onChange={(e) => setFilterTag(e.target.value)}
|
|
||||||
>
|
|
||||||
<option value="">All Categories</option>
|
|
||||||
{allTags.map(tag => (
|
|
||||||
<option key={tag} value={tag}>{tag}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="col-md-2">
|
<div className="col-md-2">
|
||||||
<span className="text-muted">{filteredItems.length} items found</span>
|
<span className="text-muted">{filteredItems.length} items found</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -124,11 +107,6 @@ const ItemList: React.FC = () => {
|
|||||||
</h5>
|
</h5>
|
||||||
<p className="card-text text-truncate text-dark">{item.description}</p>
|
<p className="card-text text-truncate text-dark">{item.description}</p>
|
||||||
|
|
||||||
<div className="mb-2">
|
|
||||||
{item.tags && item.tags.map((tag, index) => (
|
|
||||||
<span key={index} className="badge bg-secondary me-1">{tag}</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
{item.pricePerDay && (
|
{item.pricePerDay && (
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ export interface Item {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
tags: string[];
|
|
||||||
isPortable: boolean;
|
isPortable: boolean;
|
||||||
pickUpAvailable?: boolean;
|
pickUpAvailable?: boolean;
|
||||||
localDeliveryAvailable?: boolean;
|
localDeliveryAvailable?: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user