fix contact form and appt ui
This commit is contained in:
@@ -383,6 +383,10 @@ function AppointmentsPage() {
|
||||
|
||||
const [showAddPetModal, setShowAddPetModal] = useState(false);
|
||||
const [cancellingId, setCancellingId] = useState(null);
|
||||
const [apptSearch, setApptSearch] = useState("");
|
||||
const [adoptionSearch, setAdoptionSearch] = useState("");
|
||||
const [showPastAppts, setShowPastAppts] = useState(false);
|
||||
const [showPastAdoptions, setShowPastAdoptions] = useState(false);
|
||||
|
||||
const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
|
||||
|
||||
@@ -966,11 +970,27 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
|
||||
<h2 className="appt-form-title">{canBookAppointments ? "Your Appointments" : "Appointments"}</h2>
|
||||
{loadingAppointments ? (
|
||||
<p className="appt-loading">Loading appointments...</p>
|
||||
) : appointments.length === 0 ? (
|
||||
<p className="appt-empty">No appointments yet.</p>
|
||||
) : (() => {
|
||||
const activeAppts = appointments.filter((a) => a.appointmentStatus?.toLowerCase() === "booked");
|
||||
const pastAppts = appointments.filter((a) => a.appointmentStatus?.toLowerCase() !== "booked");
|
||||
const q = apptSearch.toLowerCase();
|
||||
const filteredActive = activeAppts.filter((a) =>
|
||||
!q || [a.serviceName, a.storeName, a.petName].some((v) => v?.toLowerCase().includes(q))
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
className="appt-search"
|
||||
type="text"
|
||||
placeholder="Search appointments…"
|
||||
value={apptSearch}
|
||||
onChange={(e) => setApptSearch(e.target.value)}
|
||||
/>
|
||||
{filteredActive.length === 0 ? (
|
||||
<p className="appt-empty">{activeAppts.length === 0 ? "No active appointments." : "No results."}</p>
|
||||
) : (
|
||||
<div className="appt-list">
|
||||
{appointments.map((a) => (
|
||||
{filteredActive.map((a) => (
|
||||
<div key={a.appointmentId} className="appt-card">
|
||||
<div className="appt-card-header">
|
||||
<span className="appt-card-service">{a.serviceName}</span>
|
||||
@@ -983,11 +1003,8 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
|
||||
<span>{a.appointmentDate} at {formatTime(a.appointmentTime)}</span>
|
||||
</div>
|
||||
{a.petName && (
|
||||
<div className="appt-card-pets">
|
||||
Pet: {a.petName}
|
||||
</div>
|
||||
<div className="appt-card-pets">Pet: {a.petName}</div>
|
||||
)}
|
||||
{a.appointmentStatus?.toLowerCase() === "booked" && (
|
||||
<div className="appt-card-actions">
|
||||
<button
|
||||
type="button"
|
||||
@@ -998,20 +1015,66 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
|
||||
{cancellingId === a.appointmentId ? "Cancelling..." : "Cancel"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{pastAppts.length > 0 && (
|
||||
<div className="appt-past-section">
|
||||
<button className="appt-past-toggle" onClick={() => setShowPastAppts((v) => !v)}>
|
||||
{showPastAppts ? "Hide" : "Show"} past appointments ({pastAppts.length})
|
||||
</button>
|
||||
{showPastAppts && (
|
||||
<div className="appt-list appt-list--past">
|
||||
{pastAppts.map((a) => (
|
||||
<div key={a.appointmentId} className="appt-card appt-card--past">
|
||||
<div className="appt-card-header">
|
||||
<span className="appt-card-service">{a.serviceName}</span>
|
||||
<span className={`appt-card-status appt-card-status--${a.appointmentStatus?.toLowerCase()}`}>
|
||||
{a.appointmentStatus}
|
||||
</span>
|
||||
</div>
|
||||
<div className="appt-card-details">
|
||||
<span>{a.storeName}</span>
|
||||
<span>{a.appointmentDate} at {formatTime(a.appointmentTime)}</span>
|
||||
</div>
|
||||
{a.petName && (
|
||||
<div className="appt-card-pets">Pet: {a.petName}</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
|
||||
<h2 className="appt-form-title" style={{ marginTop: "2rem" }}>{canBookAppointments ? "Your Adoptions" : "Adoptions"}</h2>
|
||||
{loadingAdoptions ? (
|
||||
<p className="appt-loading">Loading adoptions...</p>
|
||||
) : adoptions.length === 0 ? (
|
||||
<p className="appt-empty">No adoption requests yet.</p>
|
||||
) : (() => {
|
||||
const activeAdoptions = adoptions.filter((a) => a.adoptionStatus?.toLowerCase() === "pending");
|
||||
const pastAdoptions = adoptions.filter((a) => a.adoptionStatus?.toLowerCase() !== "pending");
|
||||
const q = adoptionSearch.toLowerCase();
|
||||
const filteredActive = activeAdoptions.filter((a) =>
|
||||
!q || [a.petName, a.sourceStoreName].some((v) => v?.toLowerCase().includes(q))
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
className="appt-search"
|
||||
type="text"
|
||||
placeholder="Search adoptions…"
|
||||
value={adoptionSearch}
|
||||
onChange={(e) => setAdoptionSearch(e.target.value)}
|
||||
/>
|
||||
{filteredActive.length === 0 ? (
|
||||
<p className="appt-empty">{activeAdoptions.length === 0 ? "No active adoption requests." : "No results."}</p>
|
||||
) : (
|
||||
<div className="appt-list">
|
||||
{adoptions.map((a) => (
|
||||
{filteredActive.map((a) => (
|
||||
<div key={a.adoptionId} className="appt-card">
|
||||
<div className="appt-card-header">
|
||||
<span className="appt-card-service">{a.petName}</span>
|
||||
@@ -1023,7 +1086,6 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
|
||||
<span>{a.sourceStoreName}</span>
|
||||
<span>{a.adoptionDate}</span>
|
||||
</div>
|
||||
{a.adoptionStatus?.toLowerCase() === "pending" && (
|
||||
<div className="appt-card-actions">
|
||||
<button
|
||||
type="button"
|
||||
@@ -1034,11 +1096,38 @@ const canBookAppointments = user?.role === "CUSTOMER" || user?.role === "ADMIN";
|
||||
{cancellingId === a.adoptionId ? "Cancelling..." : "Cancel"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{pastAdoptions.length > 0 && (
|
||||
<div className="appt-past-section">
|
||||
<button className="appt-past-toggle" onClick={() => setShowPastAdoptions((v) => !v)}>
|
||||
{showPastAdoptions ? "Hide" : "Show"} past adoptions ({pastAdoptions.length})
|
||||
</button>
|
||||
{showPastAdoptions && (
|
||||
<div className="appt-list appt-list--past">
|
||||
{pastAdoptions.map((a) => (
|
||||
<div key={a.adoptionId} className="appt-card appt-card--past">
|
||||
<div className="appt-card-header">
|
||||
<span className="appt-card-service">{a.petName}</span>
|
||||
<span className={`appt-card-status appt-card-status--${a.adoptionStatus?.toLowerCase()}`}>
|
||||
{a.adoptionStatus}
|
||||
</span>
|
||||
</div>
|
||||
<div className="appt-card-details">
|
||||
<span>{a.sourceStoreName}</span>
|
||||
<span>{a.adoptionDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -38,6 +38,10 @@ export default function ContactPage() {
|
||||
|
||||
async function handleSend(e) {
|
||||
e.preventDefault();
|
||||
if (!token) {
|
||||
setSendError("Please log in to send a message.");
|
||||
return;
|
||||
}
|
||||
setSending(true);
|
||||
setSendError(null);
|
||||
try {
|
||||
@@ -72,7 +76,6 @@ export default function ContactPage() {
|
||||
<p>Phone: (03) 9000 0000</p>
|
||||
<p>Hours: Mon–Sat, 9:00 AM – 6:00 PM</p>
|
||||
|
||||
{token && (
|
||||
<div className="contact-form-section">
|
||||
<h3>Send Us a Message</h3>
|
||||
{sendSuccess ? (
|
||||
@@ -109,7 +112,6 @@ export default function ContactPage() {
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="info-card">
|
||||
|
||||
@@ -713,7 +713,7 @@ body {
|
||||
.info-content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem 3rem;
|
||||
padding: 0 2rem 1.5rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1.5rem;
|
||||
@@ -737,6 +737,7 @@ body {
|
||||
padding-left: 1.2rem;
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.info-card-grid {
|
||||
@@ -1713,6 +1714,49 @@ body {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.appt-search {
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.appt-search:focus {
|
||||
outline: none;
|
||||
border-color: #e68672;
|
||||
}
|
||||
|
||||
.appt-past-section {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.appt-past-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-size: 0.85rem;
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.appt-past-toggle:hover {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.appt-list--past {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.appt-card--past {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
/* Adoption Pet Selection */
|
||||
|
||||
.appt-adopt-grid {
|
||||
|
||||
Reference in New Issue
Block a user