refactor(ui): replace inline INPUT_CLS/BTN_DANGER/action link constants with component classes
- Replace 13 local INPUT_CLS/SELECT_CLS/LABEL_CLS/BTN_DANGER constants with app-input, app-select, app-label, app-action-danger-btn component classes (CustomFieldFilterBar, RolePresetsEditor, FieldCard, BlueprintFieldCatalog, BlueprintFieldEditor, BlueprintsClient, EstimateWizard, EstimateWorkspace- DraftEditor, DemandLineEditor, ScopeItemEditor, AssumptionEditor, ProjectWizard, BulkEditModal) - Replace inline text-blue-600/text-red-500 action link strings with app-action-edit / app-action-delete in AllocationsClient, ProjectsClient, ScenarioPlanner, ProjectDemandsTable, RolesClient, BlueprintsClient, CreateTaskModal, RateCardsClient, UsersClient, ManagementLevelsClient Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -669,7 +669,7 @@ export function ProjectsClient() {
|
|||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
<Link href={`/projects/${project.id}`} className="link-hover-underline text-xs font-medium text-blue-600 hover:text-blue-800 dark:text-blue-300 dark:hover:text-blue-200">
|
<Link href={`/projects/${project.id}`} className="app-action-edit link-hover-underline">
|
||||||
View →
|
View →
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ export function ManagementLevelsClient() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setConfirmDeleteLevel(level.id)}
|
onClick={() => setConfirmDeleteLevel(level.id)}
|
||||||
className="text-xs text-red-500 hover:text-red-700 font-medium"
|
className="app-action-delete"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -531,7 +531,7 @@ export function RateCardsClient() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setConfirmDeleteLine(line.id)}
|
onClick={() => setConfirmDeleteLine(line.id)}
|
||||||
className="text-xs text-red-500 hover:text-red-700 font-medium"
|
className="app-action-delete"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -669,7 +669,7 @@ export function UsersClient() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setDeleteTarget({ userId: user.id, userName: user.name ?? user.email })}
|
onClick={() => setDeleteTarget({ userId: user.id, userName: user.name ?? user.email })}
|
||||||
className="text-xs text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 font-medium"
|
className="app-action-delete"
|
||||||
title="Permanently delete user"
|
title="Permanently delete user"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
|
|||||||
@@ -552,12 +552,12 @@ export function AllocationsClient() {
|
|||||||
})}
|
})}
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<div className="flex items-center justify-end gap-2">
|
<div className="flex items-center justify-end gap-2">
|
||||||
<button type="button" onClick={() => openEdit(alloc)} className="text-xs font-medium text-blue-600 hover:text-blue-800 hover:underline dark:text-blue-300 dark:hover:text-blue-200">Edit</button>
|
<button type="button" onClick={() => openEdit(alloc)} className="app-action-edit">Edit</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setConfirmDelete({ single: alloc })}
|
onClick={() => setConfirmDelete({ single: alloc })}
|
||||||
disabled={singleDeletePending}
|
disabled={singleDeletePending}
|
||||||
className="text-xs font-medium text-red-500 hover:text-red-700 hover:underline disabled:opacity-50 dark:text-red-300 dark:hover:text-red-200"
|
className="app-action-delete disabled:opacity-50"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
@@ -977,7 +977,7 @@ export function AllocationsClient() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => openEdit(demand as AllocationWithDetails)}
|
onClick={() => openEdit(demand as AllocationWithDetails)}
|
||||||
className="text-xs font-medium text-blue-600 hover:text-blue-800 hover:underline dark:text-blue-300 dark:hover:text-blue-200"
|
className="app-action-edit"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
@@ -985,7 +985,7 @@ export function AllocationsClient() {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => setConfirmDelete({ single: demand as AllocationWithDetails })}
|
onClick={() => setConfirmDelete({ single: demand as AllocationWithDetails })}
|
||||||
disabled={singleDeletePending}
|
disabled={singleDeletePending}
|
||||||
className="text-xs font-medium text-red-500 hover:text-red-700 hover:underline disabled:opacity-50 dark:text-red-300 dark:hover:text-red-200"
|
className="app-action-delete disabled:opacity-50"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ import type { CatalogField } from "~/lib/blueprint-field-catalog.js";
|
|||||||
// Styles
|
// Styles
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const INPUT_CLS =
|
const INPUT_CLS = "app-input";
|
||||||
"px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder:text-gray-400 dark:placeholder:text-gray-500";
|
|
||||||
|
|
||||||
const BTN_PRIMARY =
|
const BTN_PRIMARY =
|
||||||
"px-4 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 text-sm font-medium disabled:opacity-50";
|
"px-4 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 text-sm font-medium disabled:opacity-50";
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ const FIELD_TYPES: { value: FieldType; label: string }[] = [
|
|||||||
{ value: FieldType.EMAIL, label: "Email" },
|
{ value: FieldType.EMAIL, label: "Email" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const INPUT_CLS =
|
const INPUT_CLS = "app-input";
|
||||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm";
|
|
||||||
|
|
||||||
const BTN_PRIMARY =
|
const BTN_PRIMARY =
|
||||||
"px-4 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 text-sm font-medium disabled:opacity-50";
|
"px-4 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 text-sm font-medium disabled:opacity-50";
|
||||||
@@ -27,8 +26,7 @@ const BTN_PRIMARY =
|
|||||||
const BTN_SECONDARY =
|
const BTN_SECONDARY =
|
||||||
"px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 text-sm font-medium";
|
"px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 text-sm font-medium";
|
||||||
|
|
||||||
const BTN_DANGER =
|
const BTN_DANGER = "app-action-danger-btn";
|
||||||
"px-2 py-1 text-red-500 hover:text-red-700 hover:bg-red-50 rounded text-sm transition-colors";
|
|
||||||
|
|
||||||
function makeEmptyField(order: number): BlueprintFieldDefinition {
|
function makeEmptyField(order: number): BlueprintFieldDefinition {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ import { SortableColumnHeader } from "~/components/ui/SortableColumnHeader.js";
|
|||||||
import { useTableSort } from "~/hooks/useTableSort.js";
|
import { useTableSort } from "~/hooks/useTableSort.js";
|
||||||
import { useViewPrefs } from "~/hooks/useViewPrefs.js";
|
import { useViewPrefs } from "~/hooks/useViewPrefs.js";
|
||||||
|
|
||||||
const INPUT_CLS =
|
const INPUT_CLS = "app-input";
|
||||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm";
|
|
||||||
|
|
||||||
const BTN_PRIMARY =
|
const BTN_PRIMARY =
|
||||||
"px-4 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 text-sm font-medium disabled:opacity-50";
|
"px-4 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 text-sm font-medium disabled:opacity-50";
|
||||||
@@ -430,7 +429,7 @@ export function BlueprintsClient() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={deleteMutation.isPending}
|
disabled={deleteMutation.isPending}
|
||||||
className="text-xs text-red-500 hover:text-red-700 disabled:opacity-50"
|
className="app-action-delete disabled:opacity-50"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -59,8 +59,7 @@ interface FieldCardProps {
|
|||||||
// Component
|
// Component
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const INPUT_CLS =
|
const INPUT_CLS = "app-input";
|
||||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm";
|
|
||||||
|
|
||||||
export function FieldCard({ field, overrides, onChange }: FieldCardProps) {
|
export function FieldCard({ field, overrides, onChange }: FieldCardProps) {
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|||||||
@@ -4,11 +4,8 @@ import { useState } from "react";
|
|||||||
import type { StaffingRequirement } from "@capakraken/shared";
|
import type { StaffingRequirement } from "@capakraken/shared";
|
||||||
import { uuid } from "~/lib/uuid.js";
|
import { uuid } from "~/lib/uuid.js";
|
||||||
|
|
||||||
const INPUT_CLS =
|
const INPUT_CLS = "app-input";
|
||||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm";
|
const BTN_DANGER = "app-action-danger-btn";
|
||||||
|
|
||||||
const BTN_DANGER =
|
|
||||||
"px-2 py-1 text-red-500 hover:text-red-700 hover:bg-red-50 rounded text-sm transition-colors";
|
|
||||||
|
|
||||||
function makeEmptyPreset(): StaffingRequirement {
|
function makeEmptyPreset(): StaffingRequirement {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -14,10 +14,9 @@ import { ResourceCombobox } from "~/components/ui/ResourceCombobox.js";
|
|||||||
import { trpc } from "~/lib/trpc/client.js";
|
import { trpc } from "~/lib/trpc/client.js";
|
||||||
import { uuid } from "~/lib/uuid.js";
|
import { uuid } from "~/lib/uuid.js";
|
||||||
|
|
||||||
const INPUT_CLS =
|
const INPUT_CLS = "app-input";
|
||||||
"w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition focus:border-brand-500 focus:ring-2 focus:ring-brand-100";
|
const SELECT_CLS = "app-select w-full";
|
||||||
const SELECT_CLS = INPUT_CLS;
|
const LABEL_CLS = "app-label";
|
||||||
const LABEL_CLS = "mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-500";
|
|
||||||
const STEP_LABELS = ["Setup", "Assumptions", "Scope", "Staffing", "Review"];
|
const STEP_LABELS = ["Setup", "Assumptions", "Scope", "Staffing", "Review"];
|
||||||
|
|
||||||
interface AssumptionRow {
|
interface AssumptionRow {
|
||||||
|
|||||||
@@ -39,9 +39,8 @@ interface ResourceListView {
|
|||||||
resources: ResourceOption[];
|
resources: ResourceOption[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const INPUT_CLS =
|
const INPUT_CLS = "app-input";
|
||||||
"w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition focus:border-brand-500 focus:ring-2 focus:ring-brand-100";
|
const LABEL_CLS = "app-label";
|
||||||
const LABEL_CLS = "mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-500";
|
|
||||||
|
|
||||||
function toNumber(value: string) {
|
function toNumber(value: string) {
|
||||||
const parsed = Number.parseFloat(value);
|
const parsed = Number.parseFloat(value);
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
||||||
|
|
||||||
const INPUT_CLS =
|
const INPUT_CLS = "app-input";
|
||||||
"w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition focus:border-brand-500 focus:ring-2 focus:ring-brand-100";
|
const LABEL_CLS = "app-label";
|
||||||
const LABEL_CLS = "mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-500";
|
|
||||||
|
|
||||||
export interface EditableAssumption {
|
export interface EditableAssumption {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ import type { EstimateResourceSnapshotView } from "~/components/estimates/Estima
|
|||||||
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
||||||
import { formatMoney } from "~/lib/format.js";
|
import { formatMoney } from "~/lib/format.js";
|
||||||
|
|
||||||
const INPUT_CLS =
|
const INPUT_CLS = "app-input";
|
||||||
"w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition focus:border-brand-500 focus:ring-2 focus:ring-brand-100";
|
const LABEL_CLS = "app-label";
|
||||||
const LABEL_CLS = "mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-500";
|
|
||||||
|
|
||||||
function toNumber(value: string) {
|
function toNumber(value: string) {
|
||||||
const parsed = Number.parseFloat(value);
|
const parsed = Number.parseFloat(value);
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ import { InfoTooltip } from "~/components/ui/InfoTooltip.js";
|
|||||||
import { isSpreadsheetFile } from "~/lib/excel.js";
|
import { isSpreadsheetFile } from "~/lib/excel.js";
|
||||||
import { parseScopeImport } from "~/lib/scopeImportParser.js";
|
import { parseScopeImport } from "~/lib/scopeImportParser.js";
|
||||||
|
|
||||||
const INPUT_CLS =
|
const INPUT_CLS = "app-input";
|
||||||
"w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition focus:border-brand-500 focus:ring-2 focus:ring-brand-100";
|
const LABEL_CLS = "app-label";
|
||||||
const LABEL_CLS = "mb-1.5 block text-xs font-semibold uppercase tracking-wide text-gray-500";
|
|
||||||
|
|
||||||
export interface EditableScopeItem {
|
export interface EditableScopeItem {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ export function CreateTaskModal({ onClose, onSuccess }: CreateTaskModalProps) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setUserId("")}
|
onClick={() => setUserId("")}
|
||||||
className="text-xs text-red-500 hover:text-red-700"
|
className="app-action-delete"
|
||||||
>
|
>
|
||||||
Clear
|
Clear
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ export function ProjectDemandsTable({ demands, project }: ProjectDemandsTablePro
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setEditTarget(demand as unknown as AllocationWithDetails)}
|
onClick={() => setEditTarget(demand as unknown as AllocationWithDetails)}
|
||||||
className="text-xs font-medium text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-200"
|
className="app-action-edit"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -30,13 +30,9 @@ const ALLOCATION_TYPE_OPTIONS = [
|
|||||||
{ value: "EXT", label: "EXT" },
|
{ value: "EXT", label: "EXT" },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const INPUT_CLS =
|
const INPUT_CLS = "app-input";
|
||||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm w-full";
|
const SELECT_CLS = "app-select w-full";
|
||||||
|
const LABEL_CLS = "app-label";
|
||||||
const SELECT_CLS =
|
|
||||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm w-full bg-white";
|
|
||||||
|
|
||||||
const LABEL_CLS = "block text-xs font-medium text-gray-600 mb-1";
|
|
||||||
|
|
||||||
const BTN_PRIMARY =
|
const BTN_PRIMARY =
|
||||||
"px-5 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 text-sm font-medium disabled:opacity-50 transition-colors";
|
"px-5 py-2 bg-brand-600 text-white rounded-lg hover:bg-brand-700 text-sm font-medium disabled:opacity-50 transition-colors";
|
||||||
@@ -44,8 +40,7 @@ const BTN_PRIMARY =
|
|||||||
const BTN_SECONDARY =
|
const BTN_SECONDARY =
|
||||||
"px-5 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 text-sm font-medium transition-colors";
|
"px-5 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 text-sm font-medium transition-colors";
|
||||||
|
|
||||||
const BTN_DANGER =
|
const BTN_DANGER = "app-action-danger-btn";
|
||||||
"px-2 py-1 text-red-500 hover:text-red-700 hover:bg-red-50 rounded text-sm transition-colors";
|
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -501,7 +501,7 @@ function ScenarioRowEditor({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onRestore(row.key)}
|
onClick={() => onRestore(row.key)}
|
||||||
className="text-xs text-blue-600 hover:text-blue-800 underline"
|
className="app-action-edit"
|
||||||
>
|
>
|
||||||
Restore
|
Restore
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import { FieldType } from "@capakraken/shared";
|
|||||||
import type { BlueprintFieldDefinition } from "@capakraken/shared";
|
import type { BlueprintFieldDefinition } from "@capakraken/shared";
|
||||||
import { trpc } from "~/lib/trpc/client.js";
|
import { trpc } from "~/lib/trpc/client.js";
|
||||||
|
|
||||||
const INPUT_CLS =
|
const INPUT_CLS = "app-input";
|
||||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
selectedIds: string[];
|
selectedIds: string[];
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ export function RolesClient() {
|
|||||||
setConfirmDelete(role);
|
setConfirmDelete(role);
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
}}
|
}}
|
||||||
className="text-xs text-red-500 hover:text-red-700"
|
className="app-action-delete"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ interface Props {
|
|||||||
onSetFilter: (key: string, value: string, type: FieldType) => void;
|
onSetFilter: (key: string, value: string, type: FieldType) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const INPUT_CLS =
|
const INPUT_CLS = "app-input";
|
||||||
"px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brand-500 text-sm bg-white";
|
|
||||||
|
|
||||||
export function CustomFieldFilterBar({ filterableFields, activeFilters, onSetFilter }: Props) {
|
export function CustomFieldFilterBar({ filterableFields, activeFilters, onSetFilter }: Props) {
|
||||||
if (filterableFields.length === 0) return null;
|
if (filterableFields.length === 0) return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user