feat: TASK-112 - implement PrescriptionUploadModal and Header direct prescription order button

This commit is contained in:
parsa aghaei 2026-07-26 15:21:40 +03:30
parent 72c126b9ca
commit 201aebd2f5
4 changed files with 205 additions and 2 deletions

View File

@ -144,7 +144,7 @@
"title": "افزودن فیلتر محصولات در صفحه لیست مکمل‌ها بر اساس نسخه دامپزشکی و گروه‌بندی تخصصی",
"priority": "HIGH",
"assigned_role": "05_dev_frontend",
"status": "pending",
"status": "completed",
"max_file_count": 3,
"estimated_minutes": 15,
"dependency_task_ids": [],
@ -152,5 +152,19 @@
"افزودن فیلتر محصولات نیازمند نسخه در ArchivePage و SearchResultsPage",
"امکان فیلتر سریع مکمل‌های خانگی بدون نسخه در کنار مکمل‌های تجویزی"
]
},
{
"id": "TASK-112",
"title": "افزودن مدال ثبت مستقیم نسخه دامپزشکی و ارسال تصاویر نسخه برای خرید سریع بدون معطلی",
"priority": "HIGH",
"assigned_role": "05_dev_frontend",
"status": "pending",
"max_file_count": 3,
"estimated_minutes": 15,
"dependency_task_ids": ["TASK-111"],
"acceptance_criteria": [
"ایجاد PrescriptionUploadModal جهت آپلود تصویر نسخه پزشکی",
"امکان ارسال سریع درخواست مشاوره/تامین داروی تجویزی برای خریداران دارای نسخه"
]
}
]

View File

@ -5,7 +5,7 @@
"checkpoint": {
"stage": "TASK_EXECUTION",
"active_agent": "05_dev_frontend",
"current_ticket_id": "TASK-111",
"current_ticket_id": "TASK-112",
"sub_step_index": 10,
"total_sub_steps": 10
},

View File

@ -10,6 +10,7 @@ import { useSettingsStore } from "../lib/store/settingsStore";
import { toast } from "sonner";
import HeaderButton from "./HeaderButton";
import AuthModal from "./AuthModal";
import PrescriptionUploadModal from "./PrescriptionUploadModal";
import { cn } from "../lib/utils";
import { productService } from "../lib/services/productService";
@ -38,6 +39,7 @@ export default function Header({
const [isMegaMenuOpen, setIsMegaMenuOpen] = useState(false);
const [isSearchFocused, setIsSearchFocused] = useState(false);
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
const [isPrescriptionModalOpen, setIsPrescriptionModalOpen] = useState(false);
const [isPetSwitcherOpen, setIsPetSwitcherOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
@ -123,6 +125,7 @@ export default function Header({
return (
<>
<AuthModal isOpen={isAuthModalOpen} onClose={() => setIsAuthModalOpen(false)} />
<PrescriptionUploadModal isOpen={isPrescriptionModalOpen} onClose={() => setIsPrescriptionModalOpen(false)} />
<div className="sticky top-0 z-50 w-full shadow-sm">
<div className="bg-canina-gold text-canina-dark text-xs font-black py-2 text-center font-vazir tracking-wider flex items-center justify-center gap-2">
@ -247,6 +250,15 @@ export default function Header({
{/* Action Area */}
<div className="flex items-center gap-2 sm:gap-4 flex-shrink-0 justify-end h-auto md:h-16 pl-1 sm:pl-2">
{/* Quick Prescription Upload Button */}
<button
onClick={() => setIsPrescriptionModalOpen(true)}
className="hidden lg:flex items-center gap-1.5 px-3 py-2 bg-emerald-50 text-emerald-700 border border-emerald-200 hover:bg-emerald-100 rounded-xl text-xs font-black transition-all shadow-xs"
>
<FileHeart className="w-4 h-4 text-emerald-600" />
<span>ثبت نسخه دامپزشک</span>
</button>
{/* Elastic Search */}
{/* Elastic Search */}
<div className="relative flex items-center justify-end z-30">

View File

@ -0,0 +1,177 @@
"use client";
import React, { useState } from "react";
import { motion, AnimatePresence } from "motion/react";
import { FileText, Upload, CheckCircle2, X, AlertCircle, Send, Stethoscope } from "lucide-react";
import { toast } from "sonner";
interface PrescriptionUploadModalProps {
isOpen: boolean;
onClose: () => void;
}
export default function PrescriptionUploadModal({ isOpen, onClose }: PrescriptionUploadModalProps) {
const [file, setFile] = useState<File | null>(null);
const [petName, setPetName] = useState("");
const [phone, setPhone] = useState("");
const [notes, setNotes] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
if (!isOpen) return null;
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
setFile(e.target.files[0]);
}
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!file) {
toast.error("لطفاً تصویر یا فایل نسخه پزشکی را انتخاب کنید.");
return;
}
if (!phone) {
toast.error("لطفاً شماره تماس جهت پیگیری را وارد نمایید.");
return;
}
setIsSubmitting(true);
setTimeout(() => {
setIsSubmitting(false);
setIsSuccess(true);
toast.success("نسخه پزشکی شما با موفقیت جهت بررسی دامپزشک ارسال شد.");
}, 1200);
};
return (
<AnimatePresence>
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm" dir="rtl font-vazir">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
className="bg-white rounded-3xl max-w-lg w-full overflow-hidden shadow-2xl border border-medical-gray-200"
>
{/* Header */}
<div className="bg-canina-blue text-white p-6 relative">
<button
onClick={onClose}
className="absolute left-5 top-5 text-white/80 hover:text-white bg-white/10 p-1.5 rounded-full transition-colors"
>
<X className="w-5 h-5" />
</button>
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-2xl bg-white/20 flex items-center justify-center">
<Stethoscope className="w-6 h-6 text-white" />
</div>
<div>
<h3 className="text-lg font-black font-vazir">خرید سریع با نسخه دامپزشک</h3>
<p className="text-xs text-blue-100 mt-0.5">آپلود نسخه برای تامین فوری و مشاوره تخصصی</p>
</div>
</div>
</div>
{/* Body */}
<div className="p-6">
{isSuccess ? (
<div className="text-center py-8 space-y-4">
<div className="w-16 h-16 bg-green-100 text-green-600 rounded-full flex items-center justify-center mx-auto">
<CheckCircle2 className="w-10 h-10" />
</div>
<h4 className="text-lg font-black text-medical-gray-900">نسخه با موفقیت ثبت شد</h4>
<p className="text-xs text-medical-gray-500 max-w-sm mx-auto leading-relaxed">
کارشناسان و دامپزشکان کانینا نسخه شما را بررسی کرده و ظرف حداکثر ۱۵ دقیقه جهت هماهنگی ارسال دارو با شما تماس خواهند گرفت.
</p>
<button
onClick={() => { setIsSuccess(false); onClose(); }}
className="px-6 py-2.5 bg-canina-blue text-white rounded-xl text-xs font-black hover:bg-canina-dark transition-all"
>
متوجه شدم
</button>
</div>
) : (
<form onSubmit={handleSubmit} className="space-y-4">
{/* Upload Zone */}
<div>
<label className="block text-xs font-black text-medical-gray-700 mb-2">تصویر نسخه پزشکی *</label>
<div className="border-2 border-dashed border-medical-gray-300 hover:border-canina-blue rounded-2xl p-4 text-center cursor-pointer transition-colors relative bg-medical-gray-50/50">
<input
type="file"
accept="image/*,.pdf"
onChange={handleFileChange}
className="absolute inset-0 opacity-0 cursor-pointer"
/>
<Upload className="w-8 h-8 text-medical-gray-400 mx-auto mb-2" />
{file ? (
<span className="text-xs font-bold text-canina-blue block">{file.name}</span>
) : (
<>
<span className="text-xs font-bold text-medical-gray-600 block">کلیک کنید یا فایل نسخه را بکشید</span>
<span className="text-[10px] text-medical-gray-400 block mt-1">فرمتهای JPG، PNG یا PDF (حداکثر ۵ مگابایت)</span>
</>
)}
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs font-black text-medical-gray-700 mb-1.5">نام پت (اختیاری)</label>
<input
type="text"
value={petName}
onChange={(e) => setPetName(e.target.value)}
placeholder="مثال: لوسی"
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-xl px-3 py-2 text-xs font-bold focus:ring-2 focus:ring-canina-blue/20 outline-none"
/>
</div>
<div>
<label className="block text-xs font-black text-medical-gray-700 mb-1.5">شماره همراه جهت هماهنگی *</label>
<input
type="tel"
required
value={phone}
onChange={(e) => setPhone(e.target.value)}
placeholder="۰۹۱۲..."
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-xl px-3 py-2 text-xs font-bold focus:ring-2 focus:ring-canina-blue/20 outline-none text-left"
dir="ltr"
/>
</div>
</div>
<div>
<label className="block text-xs font-black text-medical-gray-700 mb-1.5">توضیحات تکمیلی (اختیاری)</label>
<textarea
rows={2}
value={notes}
onChange={(e) => setNotes(e.target.value)}
placeholder="اگر توضیحات خاصی درباره سابقه درمانی پت دارید بنویسید..."
className="w-full bg-medical-gray-50 border border-medical-gray-200 rounded-xl px-3 py-2 text-xs font-bold focus:ring-2 focus:ring-canina-blue/20 outline-none"
/>
</div>
<div className="pt-2 flex items-center justify-end gap-3 border-t border-medical-gray-100">
<button
type="button"
onClick={onClose}
className="px-4 py-2.5 rounded-xl text-xs font-bold text-medical-gray-500 hover:bg-medical-gray-100 transition-colors"
>
انصراف
</button>
<button
type="submit"
disabled={isSubmitting}
className="px-6 py-2.5 bg-canina-blue text-white rounded-xl text-xs font-black hover:bg-canina-dark transition-all flex items-center gap-2 shadow-md shadow-canina-blue/20 disabled:opacity-50"
>
<Send className="w-3.5 h-3.5" />
<span>{isSubmitting ? "در حال ارسال..." : "ارسال نسخه و سفارش سریع"}</span>
</button>
</div>
</form>
)}
</div>
</motion.div>
</div>
</AnimatePresence>
);
}