create driver page and client page also create oll driver , client layer

This commit is contained in:
m7amedez5511
2026-06-14 16:25:56 +03:00
parent 68bfce4345
commit bcc4baf28a
35 changed files with 5551 additions and 816 deletions

22
Components/UI/Badge.tsx Normal file
View File

@@ -0,0 +1,22 @@
interface BadgeProps {
label: string;
color?: "indigo" | "green" | "amber" | "red" | "slate";
}
const BADGE_COLORS: Record<string, string> = {
indigo: "bg-indigo-100 text-indigo-700",
green: "bg-emerald-100 text-emerald-700",
amber: "bg-amber-100 text-amber-700",
red: "bg-red-100 text-red-700",
slate: "bg-slate-100 text-slate-600",
};
export function Badge({ label, color = "slate" }: BadgeProps) {
return (
<span
className={`inline-block rounded-full px-2.5 py-0.5 text-xs font-semibold ${BADGE_COLORS[color]}`}
>
{label}
</span>
);
}

View File

@@ -0,0 +1,43 @@
import { Button } from "./Button";
import { Modal } from "./ModalProps";
interface ConfirmDialogProps {
open: boolean;
title: string;
description: string;
confirmLabel?: string;
onConfirm: () => void;
onCancel: () => void;
loading?: boolean;
}
export function ConfirmDialog({
open,
title,
description,
confirmLabel = "Delete",
onConfirm,
onCancel,
loading = false,
}: ConfirmDialogProps) {
return (
<Modal
open={open}
title={title}
onClose={onCancel}
size="sm"
footer={
<>
<Button variant="secondary" onClick={onCancel} disabled={loading}>
Cancel
</Button>
<Button variant="danger" onClick={onConfirm} loading={loading}>
{confirmLabel}
</Button>
</>
}
>
<p className="text-sm text-slate-600">{description}</p>
</Modal>
);
}

View File

@@ -0,0 +1,17 @@
interface EmptyStateProps {
icon?: string;
title: string;
description?: string;
action?: React.ReactNode;
}
export function EmptyState({ icon = "📋", title, description, action }: EmptyStateProps) {
return (
<div className="flex flex-col items-center justify-center gap-3 py-16 text-center">
<span className="text-5xl" aria-hidden="true">{icon}</span>
<h3 className="text-base font-semibold text-slate-700">{title}</h3>
{description && <p className="text-sm text-slate-500 max-w-xs">{description}</p>}
{action && <div className="mt-2">{action}</div>}
</div>
);
}

View File

@@ -0,0 +1,54 @@
interface ModalProps {
open: boolean;
title: string;
onClose: () => void;
children: React.ReactNode;
/** Footer slot typically action buttons */
footer?: React.ReactNode;
size?: "sm" | "md" | "lg";
}
const MODAL_SIZES = { sm: "max-w-sm", md: "max-w-lg", lg: "max-w-2xl" };
export function Modal({ open, title, onClose, children, footer, size = "md" }: ModalProps) {
if (!open) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/40 backdrop-blur-sm"
onClick={onClose}
aria-hidden="true"
/>
{/* Panel */}
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
className={`relative w-full ${MODAL_SIZES[size]} rounded-2xl bg-white shadow-2xl flex flex-col max-h-[90vh]`}
>
{/* Header */}
<div className="flex items-center justify-between border-b border-slate-100 px-6 py-4">
<h2 id="modal-title" className="text-base font-semibold text-slate-800">
{title}
</h2>
<button
onClick={onClose}
aria-label="Close"
className="text-slate-400 hover:text-slate-600 transition-colors text-xl leading-none"
>
×
</button>
</div>
{/* Body */}
<div className="flex-1 overflow-y-auto px-6 py-5">{children}</div>
{/* Footer */}
{footer && (
<div className="flex justify-end gap-2 border-t border-slate-100 px-6 py-4">
{footer}
</div>
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,29 @@
interface PageHeaderProps {
title: string;
description?: string;
action?: React.ReactNode;
backHref?: string;
backLabel?: string;
}
export function PageHeader({ title, description, action, backHref, backLabel }: PageHeaderProps) {
return (
<div className="mb-6 flex flex-col gap-1 sm:flex-row sm:items-start sm:justify-between">
<div>
{backHref && (
<a
href={backHref}
className="mb-2 inline-flex items-center gap-1 text-xs text-indigo-600 hover:text-indigo-800 font-medium"
>
{backLabel ?? "Back"}
</a>
)}
<h1 className="text-2xl font-bold tracking-tight text-slate-900">{title}</h1>
{description && (
<p className="mt-1 text-sm text-slate-500">{description}</p>
)}
</div>
{action && <div className="mt-3 sm:mt-0 shrink-0">{action}</div>}
</div>
);
}

52
Components/UI/Select.tsx Normal file
View File

@@ -0,0 +1,52 @@
import React, { forwardRef, SelectHTMLAttributes } from "react";
// ─── Select ──────────────────────────────────────────────────────────────────
interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
label?: string;
error?: string;
wrapperClassName?: string;
children: React.ReactNode;
}
export const Select = forwardRef<HTMLSelectElement, SelectProps>(function Select(
{ label, error, wrapperClassName = "", className = "", id, children, ...rest },
ref
) {
const selectId = id ?? label?.toLowerCase().replace(/\s+/g, "-");
return (
<div className={`flex flex-col gap-1 ${wrapperClassName}`}>
{label && (
<label
htmlFor={selectId}
className="text-xs font-semibold uppercase tracking-wide text-slate-500"
>
{label}
</label>
)}
<select
ref={ref}
id={selectId}
className={`
w-full rounded-lg border bg-white px-3 py-2 text-sm text-slate-800
focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500
disabled:bg-slate-50 disabled:cursor-not-allowed
${error ? "border-red-400" : "border-slate-300"}
${className}
`}
{...rest}
>
{children}
</select>
{error && <p className="text-xs text-red-600">{error}</p>}
</div>
);
});

View File

@@ -0,0 +1,41 @@
import { forwardRef } from "react";
interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
label?: string;
error?: string;
wrapperClassName?: string;
}
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea(
{ label, error, wrapperClassName = "", className = "", id, ...rest },
ref
) {
const taId = id ?? label?.toLowerCase().replace(/\s+/g, "-");
return (
<div className={`flex flex-col gap-1 ${wrapperClassName}`}>
{label && (
<label
htmlFor={taId}
className="text-xs font-semibold uppercase tracking-wide text-slate-500"
>
{label}
</label>
)}
<textarea
ref={ref}
id={taId}
rows={3}
className={`
w-full rounded-lg border bg-white px-3 py-2 text-sm text-slate-800
placeholder:text-slate-400 resize-none
focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500
disabled:bg-slate-50 disabled:text-slate-400
${error ? "border-red-400" : "border-slate-300"}
${className}
`}
{...rest}
/>
{error && <p className="text-xs text-red-600">{error}</p>}
</div>
);
});

View File

@@ -9,4 +9,8 @@ export { Button } from "./Button";
export type { ButtonProps } from "./Button";
export { Input } from "./Input";
export type { InputProps } from "./Input";
export type { InputProps } from "./Input";
export { Textarea } from "./Textarea";
export { Select } from "./Select";