Complete Phase 5: Update EventDetail and GroupDetail pages with Bootstrap
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
import { Card, Button, Row, Col, Form, Modal } from 'react-bootstrap';
|
||||||
import { useEvent } from '../../hooks/useEvents';
|
import { useEvent } from '../../hooks/useEvents';
|
||||||
import { useGroups, useCreateGroup } from '../../hooks/useGroups';
|
import { useGroups, useCreateGroup } from '../../hooks/useGroups';
|
||||||
import { useProducts, useCreateProduct } from '../../hooks/useProducts';
|
import { useProducts, useCreateProduct } from '../../hooks/useProducts';
|
||||||
@@ -14,8 +15,8 @@ export default function EventDetailPage() {
|
|||||||
const createGroup = useCreateGroup(id!);
|
const createGroup = useCreateGroup(id!);
|
||||||
const createProduct = useCreateProduct(id!);
|
const createProduct = useCreateProduct(id!);
|
||||||
|
|
||||||
const [showGroupForm, setShowGroupForm] = useState(false);
|
const [showGroupModal, setShowGroupModal] = useState(false);
|
||||||
const [showProductForm, setShowProductForm] = useState(false);
|
const [showProductModal, setShowProductModal] = useState(false);
|
||||||
const [groupForm, setGroupForm] = useState<CreateGroupRequest>({ name: '' });
|
const [groupForm, setGroupForm] = useState<CreateGroupRequest>({ name: '' });
|
||||||
const [productForm, setProductForm] = useState<CreateProductRequest>({ name: '', type: ProductType.Drink });
|
const [productForm, setProductForm] = useState<CreateProductRequest>({ name: '', type: ProductType.Drink });
|
||||||
|
|
||||||
@@ -23,143 +24,173 @@ export default function EventDetailPage() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await createGroup.mutateAsync(groupForm);
|
await createGroup.mutateAsync(groupForm);
|
||||||
setGroupForm({ name: '' });
|
setGroupForm({ name: '' });
|
||||||
setShowGroupForm(false);
|
setShowGroupModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateProduct = async (e: React.FormEvent) => {
|
const handleCreateProduct = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await createProduct.mutateAsync(productForm);
|
await createProduct.mutateAsync(productForm);
|
||||||
setProductForm({ name: '', type: ProductType.Drink });
|
setProductForm({ name: '', type: ProductType.Drink });
|
||||||
setShowProductForm(false);
|
setShowProductModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!event) return <div className="p-8">Loading...</div>;
|
if (!event) return <div className="text-center py-5"><div className="spinner-border" role="status"><span className="visually-hidden">Loading...</span></div></div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<>
|
||||||
<div className="mb-8">
|
<div className="mb-4">
|
||||||
<h1 className="text-3xl font-bold text-gray-900">{event.name}</h1>
|
<h1>{event.name}</h1>
|
||||||
<p className="text-gray-600 mt-2">{event.location}</p>
|
<p className="text-muted">{event.location}</p>
|
||||||
|
<p className="text-muted">
|
||||||
|
{new Date(event.startDate).toLocaleDateString()} - {new Date(event.endDate).toLocaleDateString()}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
<Row>
|
||||||
{/* Groups Section */}
|
<Col lg={6} className="mb-4">
|
||||||
<div>
|
<Card className="shadow-sm h-100">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<Card.Header className="bg-primary text-white d-flex justify-content-between align-items-center">
|
||||||
<h2 className="text-2xl font-semibold">Groups</h2>
|
<h5 className="mb-0">Groups</h5>
|
||||||
<button
|
<Button variant="light" size="sm" onClick={() => setShowGroupModal(true)}>
|
||||||
onClick={() => setShowGroupForm(!showGroupForm)}
|
Add Group
|
||||||
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 text-sm"
|
</Button>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Body>
|
||||||
|
<div className="d-grid gap-3">
|
||||||
|
{groups?.map((group) => (
|
||||||
|
<Card
|
||||||
|
key={group.id}
|
||||||
|
className="border-0 shadow-sm"
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => navigate(`/groups/${group.id}`)}
|
||||||
>
|
>
|
||||||
{showGroupForm ? 'Cancel' : 'Add Group'}
|
<Card.Body>
|
||||||
</button>
|
<h6 className="mb-1">{group.name}</h6>
|
||||||
|
{group.contactPersonName && (
|
||||||
|
<small className="text-muted d-block">{group.contactPersonName}</small>
|
||||||
|
)}
|
||||||
|
<span className="badge bg-secondary mt-2">{group.peopleCount || 0} people</span>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
{(!groups || groups.length === 0) && (
|
||||||
|
<p className="text-muted text-center py-3">No groups yet</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
|
||||||
{showGroupForm && (
|
<Col lg={6} className="mb-4">
|
||||||
<div className="bg-white p-4 rounded-lg shadow mb-4">
|
<Card className="shadow-sm h-100">
|
||||||
<form onSubmit={handleCreateGroup} className="space-y-3">
|
<Card.Header className="bg-success text-white d-flex justify-content-between align-items-center">
|
||||||
<input
|
<h5 className="mb-0">Products</h5>
|
||||||
|
<Button variant="light" size="sm" onClick={() => setShowProductModal(true)}>
|
||||||
|
Add Product
|
||||||
|
</Button>
|
||||||
|
</Card.Header>
|
||||||
|
<Card.Body>
|
||||||
|
<div className="d-grid gap-3">
|
||||||
|
{products?.map((product) => (
|
||||||
|
<Card key={product.id} className="border-0 shadow-sm">
|
||||||
|
<Card.Body>
|
||||||
|
<h6 className="mb-1">{product.name}</h6>
|
||||||
|
<span className="badge bg-info">{ProductType[product.type]}</span>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
{(!products || products.length === 0) && (
|
||||||
|
<p className="text-muted text-center py-3">No products yet</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{/* Group Modal */}
|
||||||
|
<Modal show={showGroupModal} onHide={() => setShowGroupModal(false)}>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title>Add Group</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<Form onSubmit={handleCreateGroup}>
|
||||||
|
<Form.Group className="mb-3">
|
||||||
|
<Form.Label>Group Name</Form.Label>
|
||||||
|
<Form.Control
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Group Name"
|
|
||||||
required
|
required
|
||||||
value={groupForm.name}
|
value={groupForm.name}
|
||||||
onChange={(e) => setGroupForm({ ...groupForm, name: e.target.value })}
|
onChange={(e) => setGroupForm({ ...groupForm, name: e.target.value })}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg"
|
|
||||||
/>
|
/>
|
||||||
<input
|
</Form.Group>
|
||||||
|
<Form.Group className="mb-3">
|
||||||
|
<Form.Label>Contact Person (optional)</Form.Label>
|
||||||
|
<Form.Control
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Contact Person (optional)"
|
|
||||||
value={groupForm.contactPersonName || ''}
|
value={groupForm.contactPersonName || ''}
|
||||||
onChange={(e) => setGroupForm({ ...groupForm, contactPersonName: e.target.value })}
|
onChange={(e) => setGroupForm({ ...groupForm, contactPersonName: e.target.value })}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg"
|
|
||||||
/>
|
/>
|
||||||
<input
|
</Form.Group>
|
||||||
|
<Form.Group className="mb-3">
|
||||||
|
<Form.Label>Contact Email (optional)</Form.Label>
|
||||||
|
<Form.Control
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="Contact Email (optional)"
|
|
||||||
value={groupForm.contactEmail || ''}
|
value={groupForm.contactEmail || ''}
|
||||||
onChange={(e) => setGroupForm({ ...groupForm, contactEmail: e.target.value })}
|
onChange={(e) => setGroupForm({ ...groupForm, contactEmail: e.target.value })}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg"
|
|
||||||
/>
|
/>
|
||||||
<button
|
</Form.Group>
|
||||||
type="submit"
|
<div className="d-flex justify-content-end gap-2">
|
||||||
className="w-full bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700"
|
<Button variant="secondary" onClick={() => setShowGroupModal(false)}>
|
||||||
>
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button variant="primary" type="submit">
|
||||||
Create Group
|
Create Group
|
||||||
</button>
|
</Button>
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</Form>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<div className="space-y-3">
|
{/* Product Modal */}
|
||||||
{groups?.map((group) => (
|
<Modal show={showProductModal} onHide={() => setShowProductModal(false)}>
|
||||||
<div
|
<Modal.Header closeButton>
|
||||||
key={group.id}
|
<Modal.Title>Add Product</Modal.Title>
|
||||||
onClick={() => navigate(`/groups/${group.id}`)}
|
</Modal.Header>
|
||||||
className="bg-white p-4 rounded-lg shadow cursor-pointer hover:shadow-lg transition-shadow"
|
<Modal.Body>
|
||||||
>
|
<Form onSubmit={handleCreateProduct}>
|
||||||
<h3 className="font-semibold text-lg">{group.name}</h3>
|
<Form.Group className="mb-3">
|
||||||
{group.contactPersonName && (
|
<Form.Label>Product Name</Form.Label>
|
||||||
<p className="text-sm text-gray-600">{group.contactPersonName}</p>
|
<Form.Control
|
||||||
)}
|
|
||||||
<p className="text-sm text-gray-500 mt-2">{group.peopleCount || 0} people</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Products Section */}
|
|
||||||
<div>
|
|
||||||
<div className="flex justify-between items-center mb-4">
|
|
||||||
<h2 className="text-2xl font-semibold">Products</h2>
|
|
||||||
<button
|
|
||||||
onClick={() => setShowProductForm(!showProductForm)}
|
|
||||||
className="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 text-sm"
|
|
||||||
>
|
|
||||||
{showProductForm ? 'Cancel' : 'Add Product'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{showProductForm && (
|
|
||||||
<div className="bg-white p-4 rounded-lg shadow mb-4">
|
|
||||||
<form onSubmit={handleCreateProduct} className="space-y-3">
|
|
||||||
<input
|
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Product Name"
|
|
||||||
required
|
required
|
||||||
value={productForm.name}
|
value={productForm.name}
|
||||||
onChange={(e) => setProductForm({ ...productForm, name: e.target.value })}
|
onChange={(e) => setProductForm({ ...productForm, name: e.target.value })}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg"
|
|
||||||
/>
|
/>
|
||||||
<select
|
</Form.Group>
|
||||||
|
<Form.Group className="mb-3">
|
||||||
|
<Form.Label>Product Type</Form.Label>
|
||||||
|
<Form.Select
|
||||||
value={productForm.type}
|
value={productForm.type}
|
||||||
onChange={(e) => setProductForm({ ...productForm, type: parseInt(e.target.value) })}
|
onChange={(e) => setProductForm({ ...productForm, type: parseInt(e.target.value) })}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg"
|
|
||||||
>
|
>
|
||||||
<option value={ProductType.Access}>Access</option>
|
<option value={ProductType.Access}>Access</option>
|
||||||
<option value={ProductType.Drink}>Drink</option>
|
<option value={ProductType.Drink}>Drink</option>
|
||||||
<option value={ProductType.Meal}>Meal</option>
|
<option value={ProductType.Meal}>Meal</option>
|
||||||
<option value={ProductType.Special}>Special</option>
|
<option value={ProductType.Special}>Special</option>
|
||||||
</select>
|
</Form.Select>
|
||||||
<button
|
</Form.Group>
|
||||||
type="submit"
|
<div className="d-flex justify-content-end gap-2">
|
||||||
className="w-full bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700"
|
<Button variant="secondary" onClick={() => setShowProductModal(false)}>
|
||||||
>
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button variant="success" type="submit">
|
||||||
Create Product
|
Create Product
|
||||||
</button>
|
</Button>
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
|
||||||
{products?.map((product) => (
|
|
||||||
<div key={product.id} className="bg-white p-4 rounded-lg shadow">
|
|
||||||
<h3 className="font-semibold text-lg">{product.name}</h3>
|
|
||||||
<p className="text-sm text-gray-500">{ProductType[product.type]}</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</Form>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { Card, Button, Form, Modal, ListGroup, Badge, Row, Col } from 'react-bootstrap';
|
||||||
import { useGroup, useCreatePerson, useAssignQuota } from '../../hooks/useGroups';
|
import { useGroup, useCreatePerson, useAssignQuota } from '../../hooks/useGroups';
|
||||||
import { useProducts } from '../../hooks/useProducts';
|
import { useProducts } from '../../hooks/useProducts';
|
||||||
import type { CreatePersonRequest, Person } from '../../lib/types';
|
import type { CreatePersonRequest, Person } from '../../lib/types';
|
||||||
@@ -10,13 +11,12 @@ export default function GroupDetailPage() {
|
|||||||
const createPerson = useCreatePerson(id!);
|
const createPerson = useCreatePerson(id!);
|
||||||
const assignQuota = useAssignQuota();
|
const assignQuota = useAssignQuota();
|
||||||
|
|
||||||
const [showPersonForm, setShowPersonForm] = useState(false);
|
const [showPersonModal, setShowPersonModal] = useState(false);
|
||||||
const [personForm, setPersonForm] = useState<CreatePersonRequest>({ name: '' });
|
const [personForm, setPersonForm] = useState<CreatePersonRequest>({ name: '' });
|
||||||
const [selectedPerson, setSelectedPerson] = useState<Person | null>(null);
|
const [selectedPerson, setSelectedPerson] = useState<Person | null>(null);
|
||||||
const [quotaProductId, setQuotaProductId] = useState('');
|
const [quotaProductId, setQuotaProductId] = useState('');
|
||||||
const [quotaAmount, setQuotaAmount] = useState(1);
|
const [quotaAmount, setQuotaAmount] = useState(1);
|
||||||
|
|
||||||
// Get event ID from group data
|
|
||||||
const eventId = group?.eventId || '';
|
const eventId = group?.eventId || '';
|
||||||
const { data: products } = useProducts(eventId);
|
const { data: products } = useProducts(eventId);
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ export default function GroupDetailPage() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await createPerson.mutateAsync(personForm);
|
await createPerson.mutateAsync(personForm);
|
||||||
setPersonForm({ name: '' });
|
setPersonForm({ name: '' });
|
||||||
setShowPersonForm(false);
|
setShowPersonModal(false);
|
||||||
refetch();
|
refetch();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,119 +42,79 @@ export default function GroupDetailPage() {
|
|||||||
alert('Quota assigned successfully!');
|
alert('Quota assigned successfully!');
|
||||||
setQuotaProductId('');
|
setQuotaProductId('');
|
||||||
setQuotaAmount(1);
|
setQuotaAmount(1);
|
||||||
|
setSelectedPerson(null);
|
||||||
refetch();
|
refetch();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('Failed to assign quota');
|
alert('Failed to assign quota');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!group) return <div className="p-8">Loading...</div>;
|
if (!group) return <div className="text-center py-5"><div className="spinner-border" role="status"><span className="visually-hidden">Loading...</span></div></div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<>
|
||||||
<div className="mb-8">
|
<div className="mb-4">
|
||||||
<h1 className="text-3xl font-bold text-gray-900">{group.name}</h1>
|
<h1>{group.name}</h1>
|
||||||
{group.contactPersonName && (
|
{group.contactPersonName && (
|
||||||
<p className="text-gray-600 mt-2">Contact: {group.contactPersonName}</p>
|
<p className="text-muted mb-1">Contact: {group.contactPersonName}</p>
|
||||||
)}
|
)}
|
||||||
{group.contactEmail && (
|
{group.contactEmail && (
|
||||||
<p className="text-gray-600">{group.contactEmail}</p>
|
<p className="text-muted">{group.contactEmail}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-between items-center mb-6">
|
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||||
<h2 className="text-2xl font-semibold">People ({group.people?.length || 0})</h2>
|
<h4>People ({group.people?.length || 0})</h4>
|
||||||
<button
|
<Button variant="primary" onClick={() => setShowPersonModal(true)}>
|
||||||
onClick={() => setShowPersonForm(!showPersonForm)}
|
Add Person
|
||||||
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700"
|
</Button>
|
||||||
>
|
|
||||||
{showPersonForm ? 'Cancel' : 'Add Person'}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showPersonForm && (
|
<Row className="g-4">
|
||||||
<div className="bg-white p-6 rounded-lg shadow mb-6">
|
|
||||||
<h3 className="text-lg font-semibold mb-4">Add New Person</h3>
|
|
||||||
<form onSubmit={handleCreatePerson} className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Name *</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
required
|
|
||||||
value={personForm.name}
|
|
||||||
onChange={(e) => setPersonForm({ ...personForm, name: e.target.value })}
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Email</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
value={personForm.email || ''}
|
|
||||||
onChange={(e) => setPersonForm({ ...personForm, email: e.target.value })}
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Phone Number</label>
|
|
||||||
<input
|
|
||||||
type="tel"
|
|
||||||
value={personForm.phoneNumber || ''}
|
|
||||||
onChange={(e) => setPersonForm({ ...personForm, phoneNumber: e.target.value })}
|
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={createPerson.isPending}
|
|
||||||
className="w-full bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 disabled:opacity-50"
|
|
||||||
>
|
|
||||||
{createPerson.isPending ? 'Adding...' : 'Add Person'}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
||||||
{group.people?.map((person) => (
|
{group.people?.map((person) => (
|
||||||
<div key={person.id} className="bg-white p-6 rounded-lg shadow">
|
<Col key={person.id} lg={6}>
|
||||||
<div className="flex justify-between items-start mb-4">
|
<Card className="shadow-sm h-100">
|
||||||
|
<Card.Body>
|
||||||
|
<div className="d-flex justify-content-between align-items-start mb-3">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold text-gray-900">{person.name}</h3>
|
<h5 className="mb-1">{person.name}</h5>
|
||||||
{person.email && <p className="text-sm text-gray-600">{person.email}</p>}
|
{person.email && <small className="text-muted d-block">{person.email}</small>}
|
||||||
{person.phoneNumber && <p className="text-sm text-gray-600">{person.phoneNumber}</p>}
|
{person.phoneNumber && <small className="text-muted d-block">{person.phoneNumber}</small>}
|
||||||
<p className="text-xs text-gray-500 mt-2 font-mono">QR: {person.qrCode}</p>
|
<small className="font-monospace text-muted d-block mt-2">
|
||||||
|
QR: {person.qrCode.substring(0, 8)}...
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<Button
|
||||||
|
variant={selectedPerson?.id === person.id ? 'secondary' : 'outline-primary'}
|
||||||
|
size="sm"
|
||||||
onClick={() => setSelectedPerson(selectedPerson?.id === person.id ? null : person)}
|
onClick={() => setSelectedPerson(selectedPerson?.id === person.id ? null : person)}
|
||||||
className="text-blue-600 hover:text-blue-700 text-sm font-medium"
|
|
||||||
>
|
>
|
||||||
{selectedPerson?.id === person.id ? 'Close' : 'Assign Quota'}
|
{selectedPerson?.id === person.id ? 'Close' : 'Assign Quota'}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{person.quotas && person.quotas.length > 0 && (
|
{person.quotas && person.quotas.length > 0 && (
|
||||||
<div className="border-t pt-4 mt-4">
|
<div className="border-top pt-3">
|
||||||
<h4 className="text-sm font-semibold text-gray-700 mb-2">Current Quotas</h4>
|
<h6 className="mb-2">Current Quotas</h6>
|
||||||
<div className="space-y-2">
|
<ListGroup variant="flush">
|
||||||
{person.quotas.map((quota) => (
|
{person.quotas.map((quota) => (
|
||||||
<div key={quota.productId} className="flex justify-between text-sm">
|
<ListGroup.Item key={quota.productId} className="px-0 d-flex justify-content-between">
|
||||||
<span>{quota.productName}</span>
|
<span>{quota.productName}</span>
|
||||||
<span className="font-semibold">{quota.remainingAmount}/{quota.initialAmount}</span>
|
<Badge bg="primary">{quota.remainingAmount}/{quota.initialAmount}</Badge>
|
||||||
</div>
|
</ListGroup.Item>
|
||||||
))}
|
))}
|
||||||
</div>
|
</ListGroup>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedPerson?.id === person.id && (
|
{selectedPerson?.id === person.id && (
|
||||||
<div className="border-t pt-4 mt-4">
|
<div className="border-top pt-3 mt-3">
|
||||||
<h4 className="text-sm font-semibold text-gray-700 mb-3">Assign New Quota</h4>
|
<h6 className="mb-3">Assign New Quota</h6>
|
||||||
<div className="space-y-3">
|
<Form.Group className="mb-2">
|
||||||
<select
|
<Form.Select
|
||||||
|
size="sm"
|
||||||
value={quotaProductId}
|
value={quotaProductId}
|
||||||
onChange={(e) => setQuotaProductId(e.target.value)}
|
onChange={(e) => setQuotaProductId(e.target.value)}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
|
|
||||||
>
|
>
|
||||||
<option value="">Select Product</option>
|
<option value="">Select Product</option>
|
||||||
{products?.map((product) => (
|
{products?.map((product) => (
|
||||||
@@ -162,40 +122,89 @@ export default function GroupDetailPage() {
|
|||||||
{product.name}
|
{product.name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</Form.Select>
|
||||||
<input
|
</Form.Group>
|
||||||
|
<Form.Group className="mb-2">
|
||||||
|
<Form.Control
|
||||||
type="number"
|
type="number"
|
||||||
|
size="sm"
|
||||||
min="1"
|
min="1"
|
||||||
value={quotaAmount}
|
value={quotaAmount}
|
||||||
onChange={(e) => setQuotaAmount(parseInt(e.target.value))}
|
onChange={(e) => setQuotaAmount(parseInt(e.target.value))}
|
||||||
placeholder="Amount"
|
placeholder="Amount"
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
|
|
||||||
/>
|
/>
|
||||||
<button
|
</Form.Group>
|
||||||
|
<Button
|
||||||
|
variant="success"
|
||||||
|
size="sm"
|
||||||
|
className="w-100"
|
||||||
onClick={() => handleAssignQuota(person.id)}
|
onClick={() => handleAssignQuota(person.id)}
|
||||||
disabled={!quotaProductId || assignQuota.isPending}
|
disabled={!quotaProductId || assignQuota.isPending}
|
||||||
className="w-full bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 disabled:opacity-50 text-sm"
|
|
||||||
>
|
>
|
||||||
{assignQuota.isPending ? 'Assigning...' : 'Assign Quota'}
|
{assignQuota.isPending ? 'Assigning...' : 'Assign Quota'}
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
))}
|
))}
|
||||||
</div>
|
</Row>
|
||||||
|
|
||||||
{(!group.people || group.people.length === 0) && !showPersonForm && (
|
{(!group.people || group.people.length === 0) && (
|
||||||
<div className="text-center py-12 bg-gray-50 rounded-lg">
|
<Card className="text-center py-5 shadow-sm">
|
||||||
<p className="text-gray-500">No people in this group yet.</p>
|
<Card.Body>
|
||||||
<button
|
<p className="text-muted mb-3">No people in this group yet.</p>
|
||||||
onClick={() => setShowPersonForm(true)}
|
<Button variant="primary" onClick={() => setShowPersonModal(true)}>
|
||||||
className="mt-4 text-blue-600 hover:text-blue-700 font-medium"
|
|
||||||
>
|
|
||||||
Add the first person
|
Add the first person
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Add Person Modal */}
|
||||||
|
<Modal show={showPersonModal} onHide={() => setShowPersonModal(false)}>
|
||||||
|
<Modal.Header closeButton>
|
||||||
|
<Modal.Title>Add Person</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<Form onSubmit={handleCreatePerson}>
|
||||||
|
<Form.Group className="mb-3">
|
||||||
|
<Form.Label>Name *</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
value={personForm.name}
|
||||||
|
onChange={(e) => setPersonForm({ ...personForm, name: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group className="mb-3">
|
||||||
|
<Form.Label>Email</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="email"
|
||||||
|
value={personForm.email || ''}
|
||||||
|
onChange={(e) => setPersonForm({ ...personForm, email: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group className="mb-3">
|
||||||
|
<Form.Label>Phone Number</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type="tel"
|
||||||
|
value={personForm.phoneNumber || ''}
|
||||||
|
onChange={(e) => setPersonForm({ ...personForm, phoneNumber: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<div className="d-flex justify-content-end gap-2">
|
||||||
|
<Button variant="secondary" onClick={() => setShowPersonModal(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button variant="primary" type="submit" disabled={createPerson.isPending}>
|
||||||
|
{createPerson.isPending ? 'Adding...' : 'Add Person'}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</Form>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user