chore: build
This commit is contained in:
@@ -2201,6 +2201,49 @@ export const getMyPointsRewards = async (): Promise<{
|
||||
}
|
||||
};
|
||||
|
||||
export const submitLivreurRating = async (
|
||||
orderId: number,
|
||||
rating: number,
|
||||
comment: string,
|
||||
): Promise<{ success: boolean; error?: string }> => {
|
||||
const token = getAuthToken();
|
||||
if (!token) return { success: false, error: "Non authentifié" };
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/orders/${orderId}/rate`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({ rating, comment }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const data = await safeJson(response);
|
||||
return { success: false, error: data.error || "Erreur" };
|
||||
}
|
||||
return { success: true };
|
||||
} catch {
|
||||
return { success: false, error: "Erreur de connexion" };
|
||||
}
|
||||
};
|
||||
|
||||
export const getOrderRatingStatus = async (
|
||||
orderId: number,
|
||||
): Promise<{ rated: boolean; rating?: number; comment?: string }> => {
|
||||
const token = getAuthToken();
|
||||
if (!token) return { rated: false };
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/orders/${orderId}/rating`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (!response.ok) return { rated: false };
|
||||
const data = await safeJson(response);
|
||||
return data;
|
||||
} catch {
|
||||
return { rated: false };
|
||||
}
|
||||
};
|
||||
|
||||
export const claimMyReward = async (poolKey: string): Promise<{
|
||||
success: boolean;
|
||||
description?: string;
|
||||
|
||||
@@ -802,6 +802,148 @@
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
RATING LIVREUR
|
||||
============================================ */
|
||||
|
||||
.rating-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
background: #f59e0b;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
margin-top: 0.75rem;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.rating-btn:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.rating-done-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.rating-done-text {
|
||||
color: #f59e0b;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.rating-star-filled {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.rating-star-empty {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.rating-modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.rating-modal-sheet {
|
||||
background: var(--surface, #1a1a2e);
|
||||
border-radius: 16px 16px 0 0;
|
||||
padding: 1.5rem 1.5rem 2.5rem;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.rating-modal-title {
|
||||
color: var(--text-primary, #fff);
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.rating-modal-subtitle {
|
||||
color: var(--text-muted, #9ca3af);
|
||||
font-size: 0.875rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.rating-stars-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.rating-star-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.rating-comment-input {
|
||||
width: 100%;
|
||||
background: var(--bg, #0f0f1a);
|
||||
border: 1px solid var(--border, #374151);
|
||||
border-radius: 6px;
|
||||
color: var(--text-primary, #fff);
|
||||
padding: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
resize: vertical;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.rating-comment-input::placeholder {
|
||||
color: var(--text-muted, #9ca3af);
|
||||
}
|
||||
|
||||
.rating-submit-btn {
|
||||
background: #f59e0b;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.rating-submit-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.rating-submit-btn:not(:disabled):hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.rating-cancel-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-muted, #9ca3af);
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
PRINT STYLES
|
||||
============================================ */
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
isUserAuthenticated,
|
||||
getProductById,
|
||||
getMediaUrl,
|
||||
submitLivreurRating,
|
||||
getOrderRatingStatus,
|
||||
} from "../../api/api";
|
||||
import type { CompletedOrder, Product } from "../../api/api_types";
|
||||
import {
|
||||
@@ -26,6 +28,7 @@ import {
|
||||
ShoppingBag,
|
||||
Camera,
|
||||
X,
|
||||
Star,
|
||||
} from "lucide-react";
|
||||
|
||||
interface OrderProduct {
|
||||
@@ -73,6 +76,11 @@ function OrderDetails() {
|
||||
const [error, setError] = useState<string>("");
|
||||
const [showVideo, setShowVideo] = useState(false);
|
||||
const [currentVideoUrl, setCurrentVideoUrl] = useState<string>("");
|
||||
const [ratingModal, setRatingModal] = useState(false);
|
||||
const [selectedStars, setSelectedStars] = useState(0);
|
||||
const [ratingComment, setRatingComment] = useState("");
|
||||
const [ratingSubmitting, setRatingSubmitting] = useState(false);
|
||||
const [ratingDone, setRatingDone] = useState<{ rating: number; comment: string } | null>(null);
|
||||
|
||||
// ✅ VÉRIFICATION INITIALE DE L'AUTHENTIFICATION
|
||||
useEffect(() => {
|
||||
@@ -315,6 +323,11 @@ function OrderDetails() {
|
||||
|
||||
console.log("✅ [ORDER DETAILS] Données mappées:", orderData);
|
||||
setOrder(orderData);
|
||||
|
||||
if (apiData.command_info?.command_status === "approved" && apiData.command_info?.livreur_assign) {
|
||||
const r = await getOrderRatingStatus(id);
|
||||
if (r.rated) setRatingDone({ rating: r.rating!, comment: r.comment ?? "" });
|
||||
}
|
||||
} else {
|
||||
console.error("❌ [ORDER DETAILS] Erreur:", result.message);
|
||||
setError(result.message || "Erreur lors du chargement");
|
||||
@@ -327,6 +340,17 @@ function OrderDetails() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmitRating = async () => {
|
||||
if (selectedStars === 0 || !order) return;
|
||||
setRatingSubmitting(true);
|
||||
const res = await submitLivreurRating(order.id, selectedStars, ratingComment);
|
||||
setRatingSubmitting(false);
|
||||
if (res.success) {
|
||||
setRatingDone({ rating: selectedStars, comment: ratingComment });
|
||||
setRatingModal(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getProductCount = (totalPrix: number): number => {
|
||||
return Math.max(1, Math.round(totalPrix / 25));
|
||||
};
|
||||
@@ -446,6 +470,22 @@ function OrderDetails() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{order.status === "approved" && order.livreur_assign && (
|
||||
ratingDone ? (
|
||||
<div className="rating-done-row">
|
||||
<Star size={14} className="rating-star-filled" />
|
||||
<span className="rating-done-text">
|
||||
Noté {ratingDone.rating}/5{ratingDone.comment ? ` · "${ratingDone.comment}"` : ""}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<button className="rating-btn" onClick={() => setRatingModal(true)}>
|
||||
<Star size={14} />
|
||||
Noter le livreur
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
|
||||
<div className="info-row">
|
||||
<div className="info-icon">
|
||||
<Calendar size={18} />
|
||||
@@ -759,6 +799,45 @@ function OrderDetails() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Modal notation livreur */}
|
||||
{ratingModal && (
|
||||
<div className="rating-modal-overlay" onClick={() => setRatingModal(false)}>
|
||||
<div className="rating-modal-sheet" onClick={(e) => e.stopPropagation()}>
|
||||
<h2 className="rating-modal-title">Noter le livreur</h2>
|
||||
<p className="rating-modal-subtitle">{order.livreur_assign}</p>
|
||||
<div className="rating-stars-row">
|
||||
{[1, 2, 3, 4, 5].map((star) => (
|
||||
<button key={star} className="rating-star-btn" onClick={() => setSelectedStars(star)}>
|
||||
<Star
|
||||
size={36}
|
||||
className={star <= selectedStars ? "rating-star-filled" : "rating-star-empty"}
|
||||
fill={star <= selectedStars ? "#f59e0b" : "none"}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<textarea
|
||||
className="rating-comment-input"
|
||||
placeholder="Commentaire (optionnel)"
|
||||
value={ratingComment}
|
||||
onChange={(e) => setRatingComment(e.target.value)}
|
||||
maxLength={500}
|
||||
rows={3}
|
||||
/>
|
||||
<button
|
||||
className="rating-submit-btn"
|
||||
onClick={handleSubmitRating}
|
||||
disabled={selectedStars === 0 || ratingSubmitting}
|
||||
>
|
||||
{ratingSubmitting ? "Envoi..." : "Envoyer ma note"}
|
||||
</button>
|
||||
<button className="rating-cancel-btn" onClick={() => setRatingModal(false)}>
|
||||
Annuler
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Modal vidéo */}
|
||||
{showVideo && currentVideoUrl && (
|
||||
<div className="video-modal" onClick={handleCloseVideo}>
|
||||
|
||||
Reference in New Issue
Block a user