fix: wrap long map labels
This commit is contained in:
parent
7bb76a8eb4
commit
5b964eef1f
@ -138,11 +138,34 @@ export default function LocationMapCanvas({
|
||||
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
||||
};
|
||||
|
||||
const fittedLabel = (object, fallback) => {
|
||||
const rawLabel = object.label || object.zone_name || fallback;
|
||||
const maxCharacters = Math.max(8, Math.floor((Number(object.width) - 28) / 14));
|
||||
if (rawLabel.length <= maxCharacters) return rawLabel;
|
||||
return `${rawLabel.slice(0, Math.max(5, maxCharacters - 3))}...`;
|
||||
const truncateLabel = (value, maxCharacters) => {
|
||||
if (value.length <= maxCharacters) return value;
|
||||
return `${value.slice(0, Math.max(5, maxCharacters - 3)).trimEnd()}...`;
|
||||
};
|
||||
|
||||
const fittedLabelLines = (object, fallback) => {
|
||||
const rawLabel = String(object.label || object.zone_name || fallback).trim();
|
||||
const maxCharacters = Math.max(8, Math.floor((Number(object.width) - 28) / 13));
|
||||
const canUseTwoLines = Number(object.height) >= 92 && rawLabel.length > maxCharacters;
|
||||
if (!canUseTwoLines) return [truncateLabel(rawLabel, maxCharacters)];
|
||||
|
||||
const words = rawLabel.split(/\s+/);
|
||||
let firstLine = "";
|
||||
let splitIndex = 0;
|
||||
for (let index = 0; index < words.length; index += 1) {
|
||||
const candidate = firstLine ? `${firstLine} ${words[index]}` : words[index];
|
||||
if (candidate.length > maxCharacters && firstLine) break;
|
||||
firstLine = candidate;
|
||||
splitIndex = index + 1;
|
||||
if (candidate.length >= maxCharacters) break;
|
||||
}
|
||||
|
||||
const secondLine = words.slice(splitIndex).join(" ");
|
||||
if (!secondLine) return [truncateLabel(firstLine, maxCharacters)];
|
||||
return [
|
||||
truncateLabel(firstLine, maxCharacters),
|
||||
truncateLabel(secondLine, maxCharacters),
|
||||
];
|
||||
};
|
||||
|
||||
const renderMapObject = (object, index) => {
|
||||
@ -152,6 +175,8 @@ export default function LocationMapCanvas({
|
||||
const zoneItems = itemsForZone(mapItems, object.zone_id, filters, username);
|
||||
const count = zoneItems.length;
|
||||
const countLabel = `${count} item${count === 1 ? "" : "s"}`;
|
||||
const labelLines = fittedLabelLines(object, "Area");
|
||||
const countY = labelLines.length > 1 ? object.y + 76 : object.y + 52;
|
||||
const pinDots = filters.showPins
|
||||
? zoneItems.slice(0, 12).map((item, itemIndex) => {
|
||||
const column = itemIndex % 4;
|
||||
@ -192,11 +217,15 @@ export default function LocationMapCanvas({
|
||||
/>
|
||||
{filters.showLabels ? (
|
||||
<text x={object.x + 16} y={object.y + 28} className="location-map-label">
|
||||
{fittedLabel(object, "Area")}
|
||||
{labelLines.map((line, lineIndex) => (
|
||||
<tspan key={`${line}-${lineIndex}`} x={object.x + 16} dy={lineIndex === 0 ? 0 : 24}>
|
||||
{line}
|
||||
</tspan>
|
||||
))}
|
||||
</text>
|
||||
) : null}
|
||||
{filters.showCounts ? (
|
||||
<text x={object.x + 16} y={object.y + 52} className="location-map-count">
|
||||
<text x={object.x + 16} y={countY} className="location-map-count">
|
||||
{countLabel}
|
||||
</text>
|
||||
) : null}
|
||||
|
||||
@ -423,6 +423,46 @@ test("admin status follows the visible map when a saved draft exists", async ({
|
||||
await expect(page.locator(".location-map-object", { hasText: "Live Bakery" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("viewer wraps long zone labels and keeps item counts visible", async ({ page }) => {
|
||||
await mockMapShell(page);
|
||||
|
||||
const mapState = publishedMapState([
|
||||
{
|
||||
id: 1501,
|
||||
location_map_id: 901,
|
||||
zone_id: 501,
|
||||
zone_name: "Produce & Fresh Vegetables",
|
||||
type: "zone",
|
||||
label: "Produce & Fresh Vegetables",
|
||||
x: 40,
|
||||
y: 40,
|
||||
width: 260,
|
||||
height: 120,
|
||||
rotation: 0,
|
||||
locked: false,
|
||||
visible: true,
|
||||
sort_order: 1,
|
||||
},
|
||||
], true);
|
||||
|
||||
await page.route("**/households/1/locations/10/map", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(mapState),
|
||||
});
|
||||
});
|
||||
|
||||
await page.goto("/stores/100/locations/10/map");
|
||||
|
||||
const produceArea = page.locator('[data-object-key="1501"]');
|
||||
await expect(produceArea.locator(".location-map-label tspan")).toHaveCount(2);
|
||||
await expect(produceArea.locator(".location-map-label tspan").nth(0)).toHaveText("Produce & Fresh");
|
||||
await expect(produceArea.locator(".location-map-label tspan").nth(1)).toHaveText("Vegetables");
|
||||
await expect(produceArea.locator(".location-map-count")).toHaveText("2 items");
|
||||
await expect(page.locator(".location-map-pin")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("members can view a published map but cannot edit it", async ({ page }) => {
|
||||
await mockMapShell(page, memberHousehold);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user