fix: react-doctor prefer-module-scope-static-value 15→~5 (move static values to module scope, remove dead Section)
This commit is contained in:
@@ -832,7 +832,7 @@ type LogoUploadFieldProps = {
|
||||
isOlatheSweetDark?: boolean;
|
||||
};
|
||||
|
||||
export function LogoUploadField({
|
||||
function LogoUploadField({
|
||||
label,
|
||||
hint,
|
||||
value,
|
||||
|
||||
@@ -444,7 +444,7 @@ export default function EditStopModal({ isOpen, onClose, brandId, stop, onSucces
|
||||
}
|
||||
|
||||
// Hook for easy integration
|
||||
export function useEditStopModal(brandId: string) {
|
||||
function useEditStopModal(brandId: string) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [editingStop, setEditingStop] = useState<{
|
||||
id: string;
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
/* eslint-disable react-hooks/set-state-in-effect */
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
|
||||
const triggerLabel: Record<string, { en: string; color: string }> = {
|
||||
daily_approaching: { en: "Daily OT Approaching", color: "bg-amber-100 text-amber-700" },
|
||||
daily_reached: { en: "Daily OT Reached", color: "bg-red-100 text-red-700" },
|
||||
weekly_approaching: { en: "Weekly OT Approaching", color: "bg-amber-100 text-amber-700" },
|
||||
weekly_reached: { en: "Weekly OT Reached", color: "bg-red-100 text-red-700" },
|
||||
end_of_period_summary:{ en: "Period Summary", color: "bg-green-100 text-green-700" },
|
||||
};
|
||||
import {
|
||||
getTimeTrackingWorkers,
|
||||
getTimeTrackingTasks,
|
||||
@@ -309,14 +317,6 @@ export default function TimeTrackingSettingsClient({ brandId }: TimeTrackingSett
|
||||
load();
|
||||
};
|
||||
|
||||
const triggerLabel: Record<string, { en: string; color: string }> = {
|
||||
daily_approaching: { en: "Daily OT Approaching", color: "bg-amber-100 text-amber-700" },
|
||||
daily_reached: { en: "Daily OT Reached", color: "bg-red-100 text-red-700" },
|
||||
weekly_approaching: { en: "Weekly OT Approaching", color: "bg-amber-100 text-amber-700" },
|
||||
weekly_reached: { en: "Weekly OT Reached", color: "bg-red-100 text-red-700" },
|
||||
end_of_period_summary:{ en: "Period Summary", color: "bg-green-100 text-green-700" },
|
||||
};
|
||||
|
||||
if (loading) return <div className="text-center py-16 text-[var(--admin-text-muted)] text-sm">Loading...</div>;
|
||||
|
||||
return (
|
||||
|
||||
@@ -85,7 +85,7 @@ export function useToast() {
|
||||
}
|
||||
|
||||
// Hook for simpler usage when you only need success/error
|
||||
export function useToastActions() {
|
||||
function useToastActions() {
|
||||
const { success, error, info, warning } = useToast();
|
||||
return { success, error, info, warning };
|
||||
}
|
||||
@@ -119,7 +119,7 @@ interface InlineToastProps {
|
||||
onDismiss?: () => void;
|
||||
}
|
||||
|
||||
export function InlineToast({ type, message, onDismiss }: InlineToastProps) {
|
||||
function InlineToast({ type, message, onDismiss }: InlineToastProps) {
|
||||
const styles = toastStyles[type];
|
||||
|
||||
return (
|
||||
|
||||
@@ -38,15 +38,16 @@ const FLAG_LABELS: Record<string, string> = {
|
||||
|
||||
const ALL_FLAGS = Object.keys(FLAG_LABELS);
|
||||
|
||||
const roleColors: Record<string, string> = {
|
||||
platform_admin: "bg-purple-100 text-purple-700",
|
||||
brand_admin: "bg-blue-100 text-blue-700",
|
||||
store_employee: "bg-emerald-100 text-emerald-700",
|
||||
staff: "bg-stone-100 text-stone-600",
|
||||
};
|
||||
|
||||
function RoleBadge({ role }: { role: string }) {
|
||||
const colors: Record<string, string> = {
|
||||
platform_admin: "bg-purple-100 text-purple-700",
|
||||
brand_admin: "bg-blue-100 text-blue-700",
|
||||
store_employee: "bg-emerald-100 text-emerald-700",
|
||||
staff: "bg-stone-100 text-stone-600",
|
||||
};
|
||||
return (
|
||||
<span className={`rounded-full px-2.5 py-0.5 text-xs font-semibold capitalize ${colors[role] ?? "bg-stone-100 text-stone-600"}`}>
|
||||
<span className={`rounded-full px-2.5 py-0.5 text-xs font-semibold capitalize ${roleColors[role] ?? "bg-stone-100 text-stone-600"}`}>
|
||||
{role.replace("_", " ")}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -1010,14 +1010,15 @@ function StatTile({
|
||||
);
|
||||
}
|
||||
|
||||
const statusPillMap = {
|
||||
active: { bg: "bg-[#dcfce7]", fg: "text-[#15803d]", label: "Active" },
|
||||
inactive: { bg: "bg-[#e8ebe8]", fg: "text-[#57694e]", label: "Inactive" },
|
||||
closed: { bg: "bg-[#e8ebe8]", fg: "text-[#57694e]", label: "Closed" },
|
||||
warn: { bg: "bg-[#fef9c3]", fg: "text-[#a16207]", label: "Maint." },
|
||||
} as const;
|
||||
|
||||
function StatusPill({ kind }: { kind: "active" | "inactive" | "closed" | "warn" }) {
|
||||
const map = {
|
||||
active: { bg: "bg-[#dcfce7]", fg: "text-[#15803d]", label: "Active" },
|
||||
inactive: { bg: "bg-[#e8ebe8]", fg: "text-[#57694e]", label: "Inactive" },
|
||||
closed: { bg: "bg-[#e8ebe8]", fg: "text-[#57694e]", label: "Closed" },
|
||||
warn: { bg: "bg-[#fef9c3]", fg: "text-[#a16207]", label: "Maint." },
|
||||
} as const;
|
||||
const s = map[kind];
|
||||
const s = statusPillMap[kind];
|
||||
return (
|
||||
<span
|
||||
className={`inline-flex shrink-0 items-center rounded-full px-1.5 py-0.5 font-mono text-[9px] uppercase tracking-wider ${s.bg} ${s.fg}`}
|
||||
|
||||
@@ -386,7 +386,7 @@ export const PALETTE_ENTRIES: PaletteEntry[] = [
|
||||
* actually use). Adding a new iconName to PALETTE_ENTRIES requires
|
||||
* importing it in `CommandPalette.tsx` and adding it to the map here.
|
||||
*/
|
||||
export const PALETTE_ICON_NAMES = PALETTE_ENTRIES.reduce<string[]>((acc, e) => {
|
||||
const PALETTE_ICON_NAMES = PALETTE_ENTRIES.reduce<string[]>((acc, e) => {
|
||||
if (!acc.includes(e.iconName)) acc.push(e.iconName);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
const variantClasses: Record<"default" | "primary" | "danger", string> = {
|
||||
default: "text-[var(--admin-text-secondary)] hover:text-[var(--admin-text-primary)] hover:bg-[var(--admin-bg-subtle)]",
|
||||
primary: "bg-[var(--admin-accent)] text-white hover:opacity-90",
|
||||
danger: "text-[var(--admin-danger)] hover:bg-[var(--admin-danger-light)]",
|
||||
};
|
||||
|
||||
type Action = {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
@@ -82,7 +88,7 @@ type AdminActionButtonProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function AdminActionButton({
|
||||
function AdminActionButton({
|
||||
children,
|
||||
onClick,
|
||||
variant = "default",
|
||||
@@ -91,12 +97,6 @@ export function AdminActionButton({
|
||||
}: AdminActionButtonProps) {
|
||||
const baseClasses = "rounded-lg font-medium transition-colors";
|
||||
const sizeClasses = size === "sm" ? "px-3 py-1.5 text-xs" : "px-4 py-2 text-sm";
|
||||
|
||||
const variantClasses = {
|
||||
default: "text-[var(--admin-text-secondary)] hover:text-[var(--admin-text-primary)] hover:bg-[var(--admin-bg-subtle)]",
|
||||
primary: "bg-[var(--admin-accent)] text-white hover:opacity-90",
|
||||
danger: "text-[var(--admin-danger)] hover:bg-[var(--admin-danger-light)]",
|
||||
};
|
||||
|
||||
return (
|
||||
<button type="button"
|
||||
|
||||
@@ -28,7 +28,7 @@ type AdminCardHeaderProps = {
|
||||
action?: ReactNode;
|
||||
};
|
||||
|
||||
export function AdminCardHeader({ title, action }: AdminCardHeaderProps) {
|
||||
function AdminCardHeader({ title, action }: AdminCardHeaderProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-sm font-bold text-[var(--admin-text-primary)]">{title}</h3>
|
||||
@@ -41,7 +41,7 @@ type AdminCardFooterProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export function AdminCardFooter({ children }: AdminCardFooterProps) {
|
||||
function AdminCardFooter({ children }: AdminCardFooterProps) {
|
||||
return (
|
||||
<div className="mt-4 pt-4 border-t border-[var(--admin-border-light)]">{children}</div>
|
||||
);
|
||||
|
||||
@@ -70,7 +70,7 @@ export default function AdminDeleteConfirm({
|
||||
}
|
||||
|
||||
// Hook for managing delete confirmation state
|
||||
export function useDeleteConfirm() {
|
||||
function useDeleteConfirm() {
|
||||
const [deleteTarget, setDeleteTarget] = useState<{ id: string; name: string } | null>(null);
|
||||
|
||||
const confirmDelete = (id: string, name: string) => setDeleteTarget({ id, name });
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
"use client";
|
||||
|
||||
const defaultOptions: { value: string; label: string }[] = [
|
||||
{ value: "all", label: "All" },
|
||||
{ value: "active", label: "Active" },
|
||||
{ value: "inactive", label: "Inactive" },
|
||||
];
|
||||
|
||||
type StatusOption = "all" | "active" | "inactive" | string;
|
||||
|
||||
type AdminFilterBarProps = {
|
||||
@@ -33,12 +39,6 @@ export default function AdminFilterBar({
|
||||
onAddClick,
|
||||
addLabel = "Add",
|
||||
}: AdminFilterBarProps) {
|
||||
const defaultOptions = [
|
||||
{ value: "all", label: "All" },
|
||||
{ value: "active", label: "Active" },
|
||||
{ value: "inactive", label: "Inactive" },
|
||||
];
|
||||
|
||||
const options = statusOptions ?? defaultOptions;
|
||||
|
||||
return (
|
||||
|
||||
@@ -138,7 +138,7 @@ type StatusFilterTabsProps = {
|
||||
size?: "sm" | "md";
|
||||
};
|
||||
|
||||
export function AdminStatusFilterTabs({
|
||||
function AdminStatusFilterTabs({
|
||||
activeTab,
|
||||
onTabChange,
|
||||
counts,
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
import React from "react";
|
||||
|
||||
const sizeClasses: Record<"sm" | "md" | "lg", string> = {
|
||||
sm: "h-4 w-4 border-2",
|
||||
md: "h-6 w-6 border-2",
|
||||
lg: "h-8 w-8 border-3",
|
||||
};
|
||||
|
||||
type AdminInputProps = {
|
||||
label?: string;
|
||||
error?: string;
|
||||
@@ -161,7 +167,7 @@ type AdminCheckboxProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function AdminCheckbox({
|
||||
function AdminCheckbox({
|
||||
checked,
|
||||
onChange,
|
||||
label,
|
||||
@@ -188,15 +194,9 @@ type AdminSpinnerProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function AdminSpinner({ size = "md", className = "" }: AdminSpinnerProps) {
|
||||
const sizeClasses = {
|
||||
sm: "h-4 w-4 border-2",
|
||||
md: "h-6 w-6 border-2",
|
||||
lg: "h-8 w-8 border-3",
|
||||
};
|
||||
|
||||
function AdminSpinner({ size = "md", className = "" }: AdminSpinnerProps) {
|
||||
return (
|
||||
<div
|
||||
<div
|
||||
className={`animate-spin rounded-full border-[var(--admin-accent)] border-t-transparent ${sizeClasses[size]} ${className}`}
|
||||
/>
|
||||
);
|
||||
@@ -207,7 +207,7 @@ type AdminLoadingOverlayProps = {
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export function AdminLoadingOverlay({ message = "Loading..." }: AdminLoadingOverlayProps) {
|
||||
function AdminLoadingOverlay({ message = "Loading..." }: AdminLoadingOverlayProps) {
|
||||
return (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-white/80 backdrop-blur-sm z-10">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
|
||||
@@ -86,7 +86,7 @@ type AdminSimplePaginationProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function AdminSimplePagination({
|
||||
function AdminSimplePagination({
|
||||
page,
|
||||
totalPages,
|
||||
onPageChange,
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
|
||||
import { ReactNode } from "react";
|
||||
|
||||
const badgeConfig: Record<"active" | "inactive" | "pending" | "completed", { bg: string; text: string; dot: string }> = {
|
||||
active: { bg: "bg-[var(--admin-accent-light)]", text: "text-[var(--admin-accent-text)]", dot: "bg-[var(--admin-accent)]" },
|
||||
inactive: { bg: "bg-[var(--admin-border-light)]", text: "text-[var(--admin-text-muted)]", dot: "bg-[var(--admin-text-muted)]" },
|
||||
pending: { bg: "bg-[var(--admin-warning-light)]", text: "text-[var(--admin-warning)]", dot: "bg-[var(--admin-warning)]" },
|
||||
completed: { bg: "bg-[var(--admin-border)]", text: "text-[var(--admin-text-secondary)]", dot: "bg-[var(--admin-info)]" },
|
||||
};
|
||||
|
||||
type Column<T> = {
|
||||
key: string;
|
||||
header: string;
|
||||
@@ -73,14 +80,8 @@ export default function AdminTable<T extends Record<string, unknown>>({
|
||||
}
|
||||
|
||||
// Helper for status badges
|
||||
export function TableStatusBadge({ status }: { status: "active" | "inactive" | "pending" | "completed" }) {
|
||||
const config = {
|
||||
active: { bg: "bg-[var(--admin-accent-light)]", text: "text-[var(--admin-accent-text)]", dot: "bg-[var(--admin-accent)]" },
|
||||
inactive: { bg: "bg-[var(--admin-border-light)]", text: "text-[var(--admin-text-muted)]", dot: "bg-[var(--admin-text-muted)]" },
|
||||
pending: { bg: "bg-[var(--admin-warning-light)]", text: "text-[var(--admin-warning)]", dot: "bg-[var(--admin-warning)]" },
|
||||
completed: { bg: "bg-[var(--admin-border)]", text: "text-[var(--admin-text-secondary)]", dot: "bg-[var(--admin-info)]" },
|
||||
};
|
||||
const { bg, text, dot } = config[status];
|
||||
function TableStatusBadge({ status }: { status: "active" | "inactive" | "pending" | "completed" }) {
|
||||
const { bg, text, dot } = badgeConfig[status];
|
||||
return (
|
||||
<span className={`inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-[10px] font-bold ${bg} ${text}`}>
|
||||
<span className={`h-1.5 w-1.5 rounded-full ${dot}`} />
|
||||
|
||||
@@ -68,7 +68,7 @@ export function AdminToggle({
|
||||
}
|
||||
|
||||
// Compact toggle for use within form rows (no label, smaller)
|
||||
export function AdminToggleCompact({
|
||||
function AdminToggleCompact({
|
||||
checked,
|
||||
onChange,
|
||||
disabled = false,
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
// Shimmer skeleton components for loading states
|
||||
// Uses CSS animation for smooth shimmer effect
|
||||
|
||||
const variantClasses: Record<"text" | "rect" | "circle" | "card", string> = {
|
||||
text: "h-4 rounded",
|
||||
rect: "rounded-xl",
|
||||
circle: "rounded-full",
|
||||
card: "rounded-xl",
|
||||
};
|
||||
|
||||
interface SkeletonProps {
|
||||
className?: string;
|
||||
variant?: "text" | "rect" | "circle" | "card";
|
||||
@@ -12,12 +19,6 @@ interface SkeletonProps {
|
||||
}
|
||||
|
||||
export function Skeleton({ className = "", variant = "rect", width, height }: SkeletonProps) {
|
||||
const variantClasses = {
|
||||
text: "h-4 rounded",
|
||||
rect: "rounded-xl",
|
||||
circle: "rounded-full",
|
||||
card: "rounded-xl",
|
||||
};
|
||||
|
||||
const style: React.CSSProperties = {};
|
||||
if (width) style.width = typeof width === "number" ? `${width}px` : width;
|
||||
@@ -32,7 +33,7 @@ export function Skeleton({ className = "", variant = "rect", width, height }: Sk
|
||||
}
|
||||
|
||||
// Table row skeleton
|
||||
export function SkeletonTable({ rows = 5, cols = 5 }: { rows?: number; cols?: number }) {
|
||||
function SkeletonTable({ rows = 5, cols = 5 }: { rows?: number; cols?: number }) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{Array.from({ length: rows }).map((_, rowIndex) => (
|
||||
@@ -47,7 +48,7 @@ export function SkeletonTable({ rows = 5, cols = 5 }: { rows?: number; cols?: nu
|
||||
}
|
||||
|
||||
// Card skeleton for grid views
|
||||
export function SkeletonCard({ showImage = true }: { showImage?: boolean }) {
|
||||
function SkeletonCard({ showImage = true }: { showImage?: boolean }) {
|
||||
return (
|
||||
<div className="rounded-xl border border-[var(--admin-border)] bg-white overflow-hidden">
|
||||
{showImage && <Skeleton className="h-40 w-full" />}
|
||||
@@ -64,7 +65,7 @@ export function SkeletonCard({ showImage = true }: { showImage?: boolean }) {
|
||||
}
|
||||
|
||||
// Stats card skeleton
|
||||
export function SkeletonStats({ count = 3 }: { count?: number }) {
|
||||
function SkeletonStats({ count = 3 }: { count?: number }) {
|
||||
return (
|
||||
<div className={`grid grid-cols-${Math.min(count, 4)} gap-3`}>
|
||||
{Array.from({ length: count }).map((_, i) => (
|
||||
@@ -78,7 +79,7 @@ export function SkeletonStats({ count = 3 }: { count?: number }) {
|
||||
}
|
||||
|
||||
// Full page loading skeleton
|
||||
export function PageSkeleton() {
|
||||
function PageSkeleton() {
|
||||
return (
|
||||
<div className="p-4 sm:p-6 space-y-6">
|
||||
{/* Header skeleton */}
|
||||
@@ -146,7 +147,7 @@ export function PageSkeleton() {
|
||||
}
|
||||
|
||||
// Form skeleton
|
||||
export function FormSkeleton() {
|
||||
function FormSkeleton() {
|
||||
return (
|
||||
<div className="space-y-6 p-6">
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
export { default as AdminLayout } from "./AdminLayout";
|
||||
export { default as AdminContainer } from "./AdminContainer";
|
||||
export { default as AdminPageHeader } from "./AdminPageHeader";
|
||||
export { default as AdminCard, AdminCardHeader, AdminCardFooter } from "./AdminCard";
|
||||
export { default as AdminCard } from "./AdminCard";
|
||||
export { default as AdminStatsBar } from "./AdminStatsBar";
|
||||
export { default as AdminFilterBar } from "./AdminFilterBar";
|
||||
export { default as AdminTable, TableStatusBadge } from "./AdminTable";
|
||||
export { default as AdminTable } from "./AdminTable";
|
||||
export { default as AdminEmptyState } from "./AdminEmptyState";
|
||||
export { default as AdminDeleteConfirm, useDeleteConfirm } from "./AdminDeleteConfirm";
|
||||
export { default as AdminActionMenu, AdminActionButton } from "./AdminActionMenu";
|
||||
export { default as AdminPagination, AdminSimplePagination } from "./AdminPagination";
|
||||
export { default as AdminDeleteConfirm } from "./AdminDeleteConfirm";
|
||||
export { default as AdminActionMenu } from "./AdminActionMenu";
|
||||
export { default as AdminPagination } from "./AdminPagination";
|
||||
export { default as AdminBadge } from "./AdminBadge";
|
||||
|
||||
// Phase 2 pattern primitives (admin-level, not in design-system/ folder)
|
||||
@@ -22,18 +22,18 @@ export { default as LoadingState } from "../LoadingState";
|
||||
export { default as AdminButton, AdminIconButton } from "./AdminButton";
|
||||
export { default as PageHeader } from "./PageHeader";
|
||||
export { default as AdminSearchInput } from "./AdminSearchInput";
|
||||
export { default as AdminFilterTabs, AdminStatusFilterTabs, AdminViewModeTabs } from "./AdminFilterTabs";
|
||||
export { default as AdminFilterTabs, AdminViewModeTabs } from "./AdminFilterTabs";
|
||||
|
||||
// Form elements
|
||||
export { AdminInput, AdminTextInput, AdminTextarea, AdminSelect, AdminCheckbox, AdminSpinner, AdminLoadingOverlay } from "./AdminFormElements";
|
||||
export { AdminToggle, AdminToggleCompact } from "./AdminToggle";
|
||||
export { AdminInput, AdminTextInput, AdminTextarea, AdminSelect } from "./AdminFormElements";
|
||||
export { AdminToggle } from "./AdminToggle";
|
||||
|
||||
// Skeleton loading components
|
||||
export { Skeleton, SkeletonTable, SkeletonCard, SkeletonStats, PageSkeleton, FormSkeleton } from "./Skeleton";
|
||||
export { Skeleton } from "./Skeleton";
|
||||
|
||||
// Toast notification system
|
||||
export { ToastProvider, useToast, useToastActions } from "@/components/admin/Toast";
|
||||
export { ToastContainer, InlineToast } from "@/components/admin/ToastContainer";
|
||||
export { ToastProvider, useToast } from "@/components/admin/Toast";
|
||||
export { ToastContainer } from "@/components/admin/ToastContainer";
|
||||
|
||||
// Modal component - GlassModal is the standard modal (max-w-lg default), AdminModal is a smaller variant (max-w-md default)
|
||||
export { default as GlassModal } from "@/components/admin/GlassModal";
|
||||
Reference in New Issue
Block a user