feat: add guest management with profiles and stay history
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,8 @@ import HousekeeperOutcomes from "./pages/Inventory/HousekeeperOutcomes.jsx";
|
||||
import RoomDashboard from "./pages/RoomDashboard/RoomDashboard.jsx";
|
||||
import Reservations from "./pages/Reservations/Reservations.jsx";
|
||||
import NewReservation from "./pages/Reservations/NewReservation.jsx";
|
||||
import Guests from "./pages/Guests/Guests.jsx";
|
||||
import GuestDetail from "./pages/Guests/GuestDetail.jsx";
|
||||
|
||||
import "./styles/global.css";
|
||||
//Submenú de Hotel
|
||||
@@ -137,6 +139,8 @@ export default function App() {
|
||||
<Route path="room-dashboard" element={<RoomDashboard />} />
|
||||
<Route path="reservations" element={<Reservations />} />
|
||||
<Route path="reservations/new" element={<NewReservation />} />
|
||||
<Route path="guests" element={<Guests />} />
|
||||
<Route path="guests/:id" element={<GuestDetail />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</>
|
||||
|
||||
363
frontend/Frontend-Hotel/src/pages/Guests/GuestDetail.jsx
Normal file
363
frontend/Frontend-Hotel/src/pages/Guests/GuestDetail.jsx
Normal file
@@ -0,0 +1,363 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { getGuestById, updateGuest, getGuestStays } from "../../services/guestService";
|
||||
|
||||
export default function GuestDetail() {
|
||||
const { t } = useTranslation();
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [guest, setGuest] = useState(null);
|
||||
const [stays, setStays] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [form, setForm] = useState({});
|
||||
|
||||
const fetchGuest = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const [guestRes, staysRes] = await Promise.all([
|
||||
getGuestById(id),
|
||||
getGuestStays(id),
|
||||
]);
|
||||
setGuest(guestRes.data.guest);
|
||||
setStays(staysRes.data.stays || []);
|
||||
setForm(guestRes.data.guest);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchGuest();
|
||||
}, [id]);
|
||||
|
||||
const handleUpdate = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await updateGuest(id, form);
|
||||
setEditing(false);
|
||||
fetchGuest();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const getInitials = (firstName, lastName) => {
|
||||
const f = firstName ? firstName.charAt(0).toUpperCase() : "";
|
||||
const l = lastName ? lastName.charAt(0).toUpperCase() : "";
|
||||
return f + l;
|
||||
};
|
||||
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return "-";
|
||||
return new Date(dateStr).toLocaleDateString();
|
||||
};
|
||||
|
||||
const renderStars = (rating) => {
|
||||
if (!rating) return "-";
|
||||
const stars = [];
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
stars.push(
|
||||
<span
|
||||
key={i}
|
||||
style={{
|
||||
color: i <= rating ? "var(--accent-gold)" : "var(--text-muted)",
|
||||
fontSize: "0.9rem",
|
||||
}}
|
||||
>
|
||||
★
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return stars;
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="empty-state">
|
||||
<p>{t("common.loading")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!guest) {
|
||||
return (
|
||||
<div className="empty-state">
|
||||
<p>{t("common.noResults")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ marginBottom: "var(--space-lg)" }}>
|
||||
<button className="btn-outline btn-sm" onClick={() => navigate("/app/guests")}>
|
||||
← {t("common.back")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Current Room Banner */}
|
||||
{guest.name_room && (
|
||||
<div
|
||||
className="card"
|
||||
style={{
|
||||
marginBottom: "var(--space-lg)",
|
||||
borderLeft: "4px solid var(--status-occupied)",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<span style={{ color: "var(--text-muted)", fontSize: "0.75rem", textTransform: "uppercase", fontWeight: 600 }}>
|
||||
{t("guests.currentRoom")}
|
||||
</span>
|
||||
<h3 style={{ margin: "4px 0 0", color: "var(--text-primary)" }}>{guest.name_room}</h3>
|
||||
</div>
|
||||
<div style={{ textAlign: "right" }}>
|
||||
<span style={{ fontSize: "0.8rem", color: "var(--text-secondary)" }}>
|
||||
{t("reservations.checkIn")}: {formatDate(guest.current_check_in)}
|
||||
</span>
|
||||
{guest.current_check_out && (
|
||||
<span style={{ display: "block", fontSize: "0.8rem", color: "var(--text-secondary)" }}>
|
||||
{t("reservations.checkOut")}: {formatDate(guest.current_check_out)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid-2">
|
||||
{/* Profile Section */}
|
||||
<div className="card">
|
||||
{!editing ? (
|
||||
<div>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "var(--space-lg)" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "var(--space-md)" }}>
|
||||
<div
|
||||
style={{
|
||||
width: "64px",
|
||||
height: "64px",
|
||||
borderRadius: "50%",
|
||||
background: "var(--accent-gold-muted)",
|
||||
color: "var(--accent-gold)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontWeight: 700,
|
||||
fontSize: "1.25rem",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{getInitials(guest.first_name, guest.last_name)}
|
||||
</div>
|
||||
<div>
|
||||
<h2 style={{ margin: 0, color: "var(--text-primary)" }}>
|
||||
{guest.first_name} {guest.last_name}
|
||||
</h2>
|
||||
{guest.nationality && (
|
||||
<span style={{ fontSize: "0.85rem", color: "var(--text-secondary)" }}>
|
||||
{guest.nationality}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<button className="btn-outline btn-sm" onClick={() => setEditing(true)}>
|
||||
{t("common.edit")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "var(--space-md)" }}>
|
||||
{guest.email && (
|
||||
<div>
|
||||
<span className="label-dark">{t("guests.email")}</span>
|
||||
<p style={{ margin: "4px 0 0", color: "var(--text-primary)", fontSize: "0.9rem" }}>{guest.email}</p>
|
||||
</div>
|
||||
)}
|
||||
{guest.phone && (
|
||||
<div>
|
||||
<span className="label-dark">{t("guests.phone")}</span>
|
||||
<p style={{ margin: "4px 0 0", color: "var(--text-primary)", fontSize: "0.9rem" }}>{guest.phone}</p>
|
||||
</div>
|
||||
)}
|
||||
{guest.id_type && (
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "var(--space-md)" }}>
|
||||
<div>
|
||||
<span className="label-dark">{t("guests.idType")}</span>
|
||||
<p style={{ margin: "4px 0 0", color: "var(--text-primary)", fontSize: "0.9rem" }}>{guest.id_type}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="label-dark">{t("guests.idNumber")}</span>
|
||||
<p style={{ margin: "4px 0 0", color: "var(--text-primary)", fontSize: "0.9rem" }}>{guest.id_number}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{guest.address && (
|
||||
<div>
|
||||
<span className="label-dark">{t("guests.address")}</span>
|
||||
<p style={{ margin: "4px 0 0", color: "var(--text-primary)", fontSize: "0.9rem" }}>{guest.address}</p>
|
||||
</div>
|
||||
)}
|
||||
{guest.notes && (
|
||||
<div>
|
||||
<span className="label-dark">{t("common.notes")}</span>
|
||||
<p style={{ margin: "4px 0 0", color: "var(--text-secondary)", fontSize: "0.9rem" }}>{guest.notes}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={handleUpdate} style={{ display: "flex", flexDirection: "column", gap: "var(--space-md)" }}>
|
||||
<h3 style={{ margin: 0, color: "var(--text-primary)" }}>{t("common.edit")}</h3>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "var(--space-md)" }}>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.firstName")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="text"
|
||||
value={form.first_name || ""}
|
||||
onChange={(e) => setForm({ ...form, first_name: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.lastName")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="text"
|
||||
value={form.last_name || ""}
|
||||
onChange={(e) => setForm({ ...form, last_name: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "var(--space-md)" }}>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.email")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="email"
|
||||
value={form.email || ""}
|
||||
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.phone")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="text"
|
||||
value={form.phone || ""}
|
||||
onChange={(e) => setForm({ ...form, phone: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "var(--space-md)" }}>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.idType")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="text"
|
||||
value={form.id_type || ""}
|
||||
onChange={(e) => setForm({ ...form, id_type: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.idNumber")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="text"
|
||||
value={form.id_number || ""}
|
||||
onChange={(e) => setForm({ ...form, id_number: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.nationality")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="text"
|
||||
value={form.nationality || ""}
|
||||
onChange={(e) => setForm({ ...form, nationality: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.address")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="text"
|
||||
value={form.address || ""}
|
||||
onChange={(e) => setForm({ ...form, address: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-dark">{t("common.notes")}</label>
|
||||
<textarea
|
||||
className="input-dark"
|
||||
rows={3}
|
||||
value={form.notes || ""}
|
||||
onChange={(e) => setForm({ ...form, notes: e.target.value })}
|
||||
style={{ resize: "vertical" }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: "var(--space-md)", justifyContent: "flex-end" }}>
|
||||
<button type="button" className="btn-outline" onClick={() => { setEditing(false); setForm(guest); }}>
|
||||
{t("common.cancel")}
|
||||
</button>
|
||||
<button type="submit" className="btn-gold">
|
||||
{t("common.save")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Stay History Section */}
|
||||
<div className="card">
|
||||
<h3 style={{ margin: "0 0 var(--space-lg)", color: "var(--text-primary)" }}>
|
||||
{t("guests.stayHistory")}
|
||||
</h3>
|
||||
{stays.length === 0 ? (
|
||||
<div className="empty-state">
|
||||
<p>{t("common.noResults")}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ overflowX: "auto" }}>
|
||||
<table className="table-dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("common.room")}</th>
|
||||
<th>{t("reservations.checkIn")}</th>
|
||||
<th>{t("reservations.checkOut")}</th>
|
||||
<th>{t("common.total")}</th>
|
||||
<th>Rating</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{stays.map((stay, idx) => (
|
||||
<tr key={stay.id || idx}>
|
||||
<td>
|
||||
<span style={{ fontWeight: 600 }}>{stay.name_room || "-"}</span>
|
||||
{stay.bed_type && (
|
||||
<span style={{ display: "block", fontSize: "0.75rem", color: "var(--text-muted)" }}>
|
||||
{stay.bed_type}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td>{formatDate(stay.check_in)}</td>
|
||||
<td>{formatDate(stay.check_out)}</td>
|
||||
<td style={{ color: "var(--accent-gold)", fontWeight: 600 }}>
|
||||
${Number(stay.total_charged || 0).toLocaleString()}
|
||||
</td>
|
||||
<td>{renderStars(stay.rating)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
277
frontend/Frontend-Hotel/src/pages/Guests/Guests.jsx
Normal file
277
frontend/Frontend-Hotel/src/pages/Guests/Guests.jsx
Normal file
@@ -0,0 +1,277 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { getGuests, createGuest } from "../../services/guestService";
|
||||
|
||||
export default function Guests() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [guests, setGuests] = useState([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [search, setSearch] = useState("");
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [form, setForm] = useState({
|
||||
first_name: "",
|
||||
last_name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
id_type: "",
|
||||
id_number: "",
|
||||
nationality: "",
|
||||
address: "",
|
||||
notes: "",
|
||||
});
|
||||
|
||||
const fetchGuests = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const params = {};
|
||||
if (search.trim()) params.search = search.trim();
|
||||
const res = await getGuests(params);
|
||||
setGuests(res.data.guests || []);
|
||||
setTotal(res.data.total || 0);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchGuests();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
fetchGuests();
|
||||
}, 400);
|
||||
return () => clearTimeout(timer);
|
||||
}, [search]);
|
||||
|
||||
const handleCreate = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await createGuest(form);
|
||||
setShowModal(false);
|
||||
setForm({
|
||||
first_name: "",
|
||||
last_name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
id_type: "",
|
||||
id_number: "",
|
||||
nationality: "",
|
||||
address: "",
|
||||
notes: "",
|
||||
});
|
||||
fetchGuests();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const getInitials = (firstName, lastName) => {
|
||||
const f = firstName ? firstName.charAt(0).toUpperCase() : "";
|
||||
const l = lastName ? lastName.charAt(0).toUpperCase() : "";
|
||||
return f + l;
|
||||
};
|
||||
|
||||
if (loading && guests.length === 0) {
|
||||
return (
|
||||
<div className="empty-state">
|
||||
<p>{t("common.loading")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="page-header">
|
||||
<h2>{t("guests.title")}</h2>
|
||||
<button className="btn-gold" onClick={() => setShowModal(true)}>
|
||||
{t("guests.newGuest")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: "var(--space-lg)" }}>
|
||||
<div className="search-bar" style={{ maxWidth: "400px" }}>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={`${t("common.search")} ${t("common.name")}, ${t("common.email")}, ${t("common.phone")}...`}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{guests.length === 0 ? (
|
||||
<div className="empty-state">
|
||||
<p>{t("common.noResults")}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid-3">
|
||||
{guests.map((guest) => (
|
||||
<div
|
||||
key={guest.id}
|
||||
className="card"
|
||||
style={{ cursor: "pointer", transition: "all 0.2s" }}
|
||||
onClick={() => navigate(`/app/guests/${guest.id}`)}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "var(--space-md)" }}>
|
||||
<div
|
||||
style={{
|
||||
width: "48px",
|
||||
height: "48px",
|
||||
borderRadius: "50%",
|
||||
background: "var(--accent-gold-muted)",
|
||||
color: "var(--accent-gold)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontWeight: 700,
|
||||
fontSize: "1rem",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{getInitials(guest.first_name, guest.last_name)}
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<h3 style={{ margin: 0, fontSize: "1rem", color: "var(--text-primary)" }}>
|
||||
{guest.first_name} {guest.last_name}
|
||||
</h3>
|
||||
{guest.email && (
|
||||
<p style={{ margin: "2px 0 0", fontSize: "0.8rem", color: "var(--text-secondary)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||
{guest.email}
|
||||
</p>
|
||||
)}
|
||||
{guest.phone && (
|
||||
<p style={{ margin: "2px 0 0", fontSize: "0.8rem", color: "var(--text-muted)" }}>
|
||||
{guest.phone}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{guest.name_room && (
|
||||
<div style={{ marginTop: "var(--space-sm)" }}>
|
||||
<span className="badge badge-info" style={{ fontSize: "0.7rem" }}>
|
||||
{guest.name_room}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* New Guest Modal */}
|
||||
{showModal && (
|
||||
<div className="modal-overlay-dark" onClick={() => setShowModal(false)}>
|
||||
<div className="modal-content-dark" onClick={(e) => e.stopPropagation()} style={{ maxWidth: "550px" }}>
|
||||
<h2>{t("guests.newGuest")}</h2>
|
||||
<form onSubmit={handleCreate} style={{ display: "flex", flexDirection: "column", gap: "var(--space-md)" }}>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "var(--space-md)" }}>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.firstName")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="text"
|
||||
required
|
||||
value={form.first_name}
|
||||
onChange={(e) => setForm({ ...form, first_name: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.lastName")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="text"
|
||||
required
|
||||
value={form.last_name}
|
||||
onChange={(e) => setForm({ ...form, last_name: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "var(--space-md)" }}>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.email")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="email"
|
||||
value={form.email}
|
||||
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.phone")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="text"
|
||||
value={form.phone}
|
||||
onChange={(e) => setForm({ ...form, phone: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "var(--space-md)" }}>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.idType")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="text"
|
||||
value={form.id_type}
|
||||
onChange={(e) => setForm({ ...form, id_type: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.idNumber")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="text"
|
||||
value={form.id_number}
|
||||
onChange={(e) => setForm({ ...form, id_number: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.nationality")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="text"
|
||||
value={form.nationality}
|
||||
onChange={(e) => setForm({ ...form, nationality: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-dark">{t("guests.address")}</label>
|
||||
<input
|
||||
className="input-dark"
|
||||
type="text"
|
||||
value={form.address}
|
||||
onChange={(e) => setForm({ ...form, address: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label-dark">{t("common.notes")}</label>
|
||||
<textarea
|
||||
className="input-dark"
|
||||
rows={3}
|
||||
value={form.notes}
|
||||
onChange={(e) => setForm({ ...form, notes: e.target.value })}
|
||||
style={{ resize: "vertical" }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: "var(--space-md)", justifyContent: "flex-end" }}>
|
||||
<button type="button" className="btn-outline" onClick={() => setShowModal(false)}>
|
||||
{t("common.cancel")}
|
||||
</button>
|
||||
<button type="submit" className="btn-gold">
|
||||
{t("common.save")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
6
frontend/Frontend-Hotel/src/services/guestService.js
Normal file
6
frontend/Frontend-Hotel/src/services/guestService.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import api from "./api";
|
||||
export const getGuests = (params) => api.get("/guests", { params });
|
||||
export const getGuestById = (id) => api.get(`/guests/${id}`);
|
||||
export const createGuest = (data) => api.post("/guests", data);
|
||||
export const updateGuest = (id, data) => api.put(`/guests/${id}`, data);
|
||||
export const getGuestStays = (id) => api.get(`/guests/${id}/stays`);
|
||||
Reference in New Issue
Block a user