feat: add Bytescale image uploads
This commit is contained in:
+160
-136
@@ -1,5 +1,8 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { BytescaleUploader } from "@/components/BytescaleUploader/BytescaleUploader";
|
||||
|
||||
import {
|
||||
getDoctorsApi,
|
||||
createDoctorApi,
|
||||
@@ -36,6 +39,7 @@ import {
|
||||
Trash,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
User,
|
||||
} from "lucide-react";
|
||||
|
||||
interface Department {
|
||||
@@ -72,6 +76,7 @@ export default function DoctorPage() {
|
||||
const [form, setForm] = useState<any>({
|
||||
doctorId: "",
|
||||
name: "",
|
||||
image: "",
|
||||
designation: "",
|
||||
workingStatus: "",
|
||||
qualification: "",
|
||||
@@ -161,6 +166,7 @@ export default function DoctorPage() {
|
||||
setForm({
|
||||
doctorId: "",
|
||||
name: "",
|
||||
image: "",
|
||||
designation: "",
|
||||
workingStatus: "",
|
||||
qualification: "",
|
||||
@@ -177,6 +183,7 @@ export default function DoctorPage() {
|
||||
setForm({
|
||||
doctorId: doc.doctorId,
|
||||
name: doc.name,
|
||||
image: doc.image || "",
|
||||
designation: doc.designation,
|
||||
workingStatus: doc.workingStatus,
|
||||
qualification: doc.qualification,
|
||||
@@ -439,155 +446,172 @@ export default function DoctorPage() {
|
||||
</Card>
|
||||
|
||||
<Dialog open={openModal} onOpenChange={setOpenModal}>
|
||||
<DialogContent className="w-full !max-w-5xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogContent className="w-full !max-w-5xl h-[90vh] flex flex-col p-0 overflow-hidden">
|
||||
<DialogHeader className="p-6 border-b bg-background z-10">
|
||||
<DialogTitle className="text-2xl">
|
||||
{editing ? "Edit Doctor" : "Add Doctor"}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mt-6">
|
||||
<div className="space-y-6">
|
||||
<h3 className="font-bold text-base border-b pb-2">
|
||||
Basic Information
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-semibold">Doctor ID</label>
|
||||
<Input
|
||||
name="doctorId"
|
||||
placeholder="GG-DOC-001"
|
||||
value={form.doctorId}
|
||||
onChange={handleChange}
|
||||
disabled={!!editing}
|
||||
className="text-base"
|
||||
/>
|
||||
<div className="flex-1 overflow-y-auto p-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div className="space-y-6">
|
||||
<h3 className="font-bold text-base border-b pb-2">
|
||||
Basic Information
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-semibold">
|
||||
Doctor Photo
|
||||
</label>
|
||||
<BytescaleUploader
|
||||
value={form.image}
|
||||
folderPath="/doctors"
|
||||
onChange={(url) => setForm({ ...form, image: url })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-semibold">Doctor ID</label>
|
||||
<Input
|
||||
name="doctorId"
|
||||
placeholder="GG-DOC-001"
|
||||
value={form.doctorId}
|
||||
onChange={handleChange}
|
||||
disabled={!!editing}
|
||||
className="text-base"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-semibold">Full Name</label>
|
||||
<Input
|
||||
name="name"
|
||||
placeholder="Dr. John Doe"
|
||||
value={form.name}
|
||||
onChange={handleChange}
|
||||
className="text-base"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-semibold">Designation</label>
|
||||
<Input
|
||||
name="designation"
|
||||
placeholder="Senior Consultant"
|
||||
value={form.designation}
|
||||
onChange={handleChange}
|
||||
className="text-base"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-semibold">
|
||||
Working Status
|
||||
</label>
|
||||
<Input
|
||||
name="workingStatus"
|
||||
placeholder="Active / On Call"
|
||||
value={form.workingStatus}
|
||||
onChange={handleChange}
|
||||
className="text-base"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-semibold">
|
||||
Qualification
|
||||
</label>
|
||||
<Input
|
||||
name="qualification"
|
||||
placeholder="MBBS, MD"
|
||||
value={form.qualification}
|
||||
onChange={handleChange}
|
||||
className="text-base"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-semibold">Full Name</label>
|
||||
<Input
|
||||
name="name"
|
||||
placeholder="Dr. John Doe"
|
||||
value={form.name}
|
||||
onChange={handleChange}
|
||||
className="text-base"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-semibold">Designation</label>
|
||||
<Input
|
||||
name="designation"
|
||||
placeholder="Senior Consultant"
|
||||
value={form.designation}
|
||||
onChange={handleChange}
|
||||
className="text-base"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-semibold">
|
||||
Working Status
|
||||
</label>
|
||||
<Input
|
||||
name="workingStatus"
|
||||
placeholder="Active / On Call"
|
||||
value={form.workingStatus}
|
||||
onChange={handleChange}
|
||||
className="text-base"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<label className="text-sm font-semibold">Qualification</label>
|
||||
<Input
|
||||
name="qualification"
|
||||
placeholder="MBBS, MD"
|
||||
value={form.qualification}
|
||||
onChange={handleChange}
|
||||
className="text-base"
|
||||
/>
|
||||
|
||||
<div className="p-5 border rounded-md bg-muted/20">
|
||||
<p className="text-base font-bold mb-4">Assign Departments</p>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{departments.map((dep) => {
|
||||
const isSelected = form.departments.some(
|
||||
(d: any) => d.departmentId === dep.departmentId,
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
key={dep.departmentId}
|
||||
type="button"
|
||||
variant={isSelected ? "default" : "outline"}
|
||||
size="sm"
|
||||
className="justify-start text-sm min-h-11 whitespace-normal break-words text-left py-2"
|
||||
onClick={() =>
|
||||
handleDepartmentToggle(dep.departmentId)
|
||||
}
|
||||
>
|
||||
{dep.name}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-5 border rounded-md bg-muted/20">
|
||||
<p className="text-base font-bold mb-4">Assign Departments</p>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{departments.map((dep) => {
|
||||
const isSelected = form.departments.some(
|
||||
(d: any) => d.departmentId === dep.departmentId,
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
key={dep.departmentId}
|
||||
type="button"
|
||||
variant={isSelected ? "default" : "outline"}
|
||||
size="sm"
|
||||
className="justify-start text-sm h-9"
|
||||
onClick={() => handleDepartmentToggle(dep.departmentId)}
|
||||
>
|
||||
{dep.name}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<h3 className="font-bold text-base border-b pb-2">
|
||||
Working Hours / Timing
|
||||
</h3>
|
||||
{form.departments.length === 0 ? (
|
||||
<div className="text-base text-muted-foreground italic py-24 text-center border-2 border-dashed rounded-lg">
|
||||
Select a department to configure timing slots
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-8">
|
||||
{form.departments.map((dep: any) => {
|
||||
const depName = departments.find(
|
||||
(d) => d.departmentId === dep.departmentId,
|
||||
)?.name;
|
||||
return (
|
||||
<div
|
||||
key={dep.departmentId}
|
||||
className="space-y-4 p-5 border rounded-lg bg-background shadow-sm"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="font-bold text-base text-primary">
|
||||
{depName}
|
||||
</p>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
Timing Slot
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-3">
|
||||
{DAYS.map((day) => (
|
||||
<div key={day} className="space-y-1">
|
||||
<label className="text-xs uppercase font-bold text-muted-foreground">
|
||||
{day}
|
||||
</label>
|
||||
<Input
|
||||
className="h-9 text-sm"
|
||||
placeholder="e.g. 09:00 AM - 01:00 PM"
|
||||
value={dep.timing?.[day] || ""}
|
||||
onChange={(e) =>
|
||||
handleTimingChange(
|
||||
dep.departmentId,
|
||||
day,
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<h3 className="font-bold text-base border-b pb-2">
|
||||
Working Hours / Timing
|
||||
</h3>
|
||||
{form.departments.length === 0 ? (
|
||||
<div className="text-base text-muted-foreground italic py-24 text-center border-2 border-dashed rounded-lg">
|
||||
Select a department to configure timing slots
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-8">
|
||||
{form.departments.map((dep: any) => {
|
||||
const depName = departments.find(
|
||||
(d) => d.departmentId === dep.departmentId,
|
||||
)?.name;
|
||||
return (
|
||||
<div
|
||||
key={dep.departmentId}
|
||||
className="space-y-4 p-5 border rounded-lg bg-background shadow-sm"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="font-bold text-base text-primary">
|
||||
{depName}
|
||||
</p>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
Timing Slot
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-3">
|
||||
{DAYS.map((day) => (
|
||||
<div key={day} className="space-y-1">
|
||||
<label className="text-xs uppercase font-bold text-muted-foreground">
|
||||
{day}
|
||||
</label>
|
||||
<Input
|
||||
className="h-9 text-sm"
|
||||
placeholder="e.g. 09:00 AM - 01:00 PM"
|
||||
value={dep.timing?.[day] || ""}
|
||||
onChange={(e) =>
|
||||
handleTimingChange(
|
||||
dep.departmentId,
|
||||
day,
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter className="mt-10 pt-6 border-t">
|
||||
<DialogFooter className="p-6 border-t bg-background z-10 mt-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setOpenModal(false)}
|
||||
|
||||
Reference in New Issue
Block a user