All files / src/components/admin/pros ProTableRow.tsx

100% Statements 7/7
100% Branches 17/17
100% Functions 3/3
100% Lines 7/7

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102                              3x                   165x 165x 165x   165x                                                       7x                                         6x                                              
import { memo } from "react";
import { Link } from "@tanstack/react-router";
import { Star, StarOff, Eye } from "lucide-react";
import { Button } from "../../ui/button";
import type { Pro } from "../../../lib/api";
 
interface ProTableRowProps {
	pro: Pro;
	onToggleFeatured: (pro: Pro) => void;
	onUpdateStatus: (
		pro: Pro,
		status: "draft" | "published" | "archived",
	) => void;
}
 
export const ProTableRow = memo(function ProTableRow({
	pro,
	onToggleFeatured,
	onUpdateStatus,
}: ProTableRowProps) {
	// #360: admins reported "blank" rows at the top of the Pros table.
	// These were pros whose onboarding flow never filled businessName (draft
	// state), so the DB stores an empty string. Rather than render nothing —
	// which makes the row unclickable-looking — fall back to the pro's slug
	// or id in muted type so admins can still identify and open the record.
	const trimmedName = pro.businessName?.trim();
	const displayName = trimmedName || pro.slug || pro.id;
	const isMissingName = !trimmedName;
 
	return (
		<tr className="border-b border-border-default last:border-0 hover:bg-background-muted">
			<td className="py-4">
				<Link
					to={`/admin/pros/${pro.id}`}
					className={`font-medium hover:text-primary-600 ${
						isMissingName
							? "text-foreground-muted italic"
							: "text-foreground-default"
					}`}
				>
					{displayName}
					{isMissingName && (
						<span className="ml-1 text-xs text-foreground-subtle not-italic">
							(no business name)
						</span>
					)}
				</Link>
			</td>
			<td className="py-4 text-foreground-muted">
				{pro.primaryLocality
					? `${pro.primaryLocality.name}, ${pro.primaryLocality.city}`
					: "-"}
			</td>
			<td className="py-4">
				<select
					value={pro.status}
					onChange={(e) =>
						onUpdateStatus(
							pro,
							e.target.value as "draft" | "published" | "archived",
						)
					}
					className={`text-xs font-medium rounded-full px-2 py-1 border-0 cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500/40 focus:border-primary-500 ${
						pro.status === "published"
							? "bg-success-light text-success"
							: pro.status === "draft"
								? "bg-warning-light text-warning"
								: "bg-background-muted text-foreground-muted"
					}`}
				>
					<option value="draft">Draft</option>
					<option value="published">Published</option>
					<option value="archived">Archived</option>
				</select>
			</td>
			<td className="py-4">
				<button
					type="button"
					onClick={() => onToggleFeatured(pro)}
					className="p-1 hover:bg-background-muted rounded"
					title={pro.isFeatured ? "Remove from featured" : "Add to featured"}
				>
					{pro.isFeatured ? (
						<Star className="h-5 w-5 text-yellow-500 fill-yellow-500" />
					) : (
						<StarOff className="h-5 w-5 text-foreground-subtle" />
					)}
				</button>
			</td>
			<td className="py-4">
				<div className="flex items-center gap-2">
					<Link to={`/admin/pros/${pro.id}`}>
						<Button variant="ghost" size="sm">
							<Eye className="h-4 w-4" />
						</Button>
					</Link>
				</div>
			</td>
		</tr>
	);
});