fix(plant-overview): real drop insertion indicator + small logo back
Three direct fixes responding to user feedback: 1. Drag-drop "simulation" — now works like Trello/Linear. As the cursor moves over a column, a live DOM placeholder node is INJECTED into the card list at the exact position the dragged card will drop. The placeholder is a 4px pulsing accent-coloured bar with a soft glow ring. Slides smoothly between cards as the cursor moves. Column body also gets a tinted background + inset accent outline for the "whole column is receptive" cue. Previous version only tinted the column — no indicator of WHERE the card would land. The new approach actually mimics the physical gesture: cards visually make room for the incoming card. 2. Customer logo restored at 32×32px. Removing it was the wrong call. It's back now as a small thumbnail avatar (rounded 10px corners, soft border, object-fit contain so wide logos don't squish). Sits to the left of the customer name in the card top row. Fallback icon for customers without a logo. Takes the same space as the step badge on the right — compact and organised. 3. Module version bumped 19.0.1.0.0 → 19.0.2.0.0 so the asset bundle content hash changes. The new compiled CSS is served at /web/assets/022171c/web.assets_backend.min.css (previously /web/assets/278b43c/...). Fresh URL forces browser to refetch — this is what was causing the "still no border" complaint. Verified in compiled CSS: o_fp_po_card_avatar, o_fp_po_drop_placeholder, o_fp_placeholder_pulse keyframes, o_fp_drop_target — all present. Zero SCSS warnings. Module upgrade clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
{
|
||||
'name': 'Fusion Plating — Shop Floor',
|
||||
'version': '19.0.1.0.0',
|
||||
'version': '19.0.2.0.0',
|
||||
'category': 'Manufacturing/Plating',
|
||||
'summary': 'Shop-floor tablet stations, QR scanning, bake window enforcer, '
|
||||
'first-piece inspection gates.',
|
||||
|
||||
@@ -95,12 +95,32 @@ export class PlantOverview extends Component {
|
||||
}
|
||||
|
||||
// ----- Drag & drop --------------------------------------------------------
|
||||
//
|
||||
// A real insertion placeholder is injected into the column body as the
|
||||
// card is dragged. The placeholder slides between cards to show exactly
|
||||
// where the dragged card will land when released. The placeholder is a
|
||||
// plain DOM node (not a reactive state field) so mouseover updates don't
|
||||
// trigger OWL re-renders.
|
||||
|
||||
_getOrCreatePlaceholder() {
|
||||
let node = document.querySelector(".o_fp_po_drop_placeholder");
|
||||
if (!node) {
|
||||
node = document.createElement("div");
|
||||
node.className = "o_fp_po_drop_placeholder";
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
_removePlaceholder() {
|
||||
document.querySelectorAll(".o_fp_po_drop_placeholder").forEach((el) => el.remove());
|
||||
}
|
||||
|
||||
onCardDragStart(card, col, ev) {
|
||||
this._draggedCard = {
|
||||
id: card.id,
|
||||
source_model: card.source_model || "mrp.workorder",
|
||||
source_wc_id: col.work_center_id,
|
||||
el: ev.target,
|
||||
};
|
||||
ev.dataTransfer.effectAllowed = "move";
|
||||
ev.dataTransfer.setData("text/plain", String(card.id));
|
||||
@@ -113,31 +133,55 @@ export class PlantOverview extends Component {
|
||||
}
|
||||
|
||||
onCardDragEnd(ev) {
|
||||
this._draggedCard = null;
|
||||
if (ev.target && ev.target.classList) {
|
||||
ev.target.classList.remove("o_fp_dragging");
|
||||
}
|
||||
// Remove any lingering drop-target highlights
|
||||
document.querySelectorAll(".o_fp_drop_target").forEach((el) => {
|
||||
el.classList.remove("o_fp_drop_target");
|
||||
});
|
||||
this._removePlaceholder();
|
||||
this._draggedCard = null;
|
||||
}
|
||||
|
||||
onColDragOver(col, ev) {
|
||||
ev.preventDefault();
|
||||
ev.dataTransfer.dropEffect = "move";
|
||||
const body = ev.currentTarget;
|
||||
if (body && !body.classList.contains("o_fp_drop_target")) {
|
||||
if (!body) return;
|
||||
if (!body.classList.contains("o_fp_drop_target")) {
|
||||
body.classList.add("o_fp_drop_target");
|
||||
}
|
||||
|
||||
// Find which card the cursor is closest to and insert the
|
||||
// placeholder above or below it. This gives the manager a
|
||||
// clear "card will land HERE" preview between existing cards.
|
||||
const placeholder = this._getOrCreatePlaceholder();
|
||||
const cards = [...body.querySelectorAll(
|
||||
".o_fp_po_card:not(.o_fp_dragging):not(.o_fp_po_drop_placeholder)",
|
||||
)];
|
||||
const y = ev.clientY;
|
||||
let insertBefore = null;
|
||||
for (const cardEl of cards) {
|
||||
const rect = cardEl.getBoundingClientRect();
|
||||
if (y < rect.top + rect.height / 2) {
|
||||
insertBefore = cardEl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (insertBefore) {
|
||||
body.insertBefore(placeholder, insertBefore);
|
||||
} else {
|
||||
body.appendChild(placeholder);
|
||||
}
|
||||
}
|
||||
|
||||
onColDragLeave(col, ev) {
|
||||
// Only remove highlight if we actually left the column body
|
||||
// (not just hovering over a child element)
|
||||
const body = ev.currentTarget;
|
||||
if (body && !body.contains(ev.relatedTarget)) {
|
||||
body.classList.remove("o_fp_drop_target");
|
||||
// Only remove placeholder if we left the body entirely —
|
||||
// otherwise the child card enter fires dragleave on the body
|
||||
this._removePlaceholder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +191,7 @@ export class PlantOverview extends Component {
|
||||
if (body) {
|
||||
body.classList.remove("o_fp_drop_target");
|
||||
}
|
||||
this._removePlaceholder();
|
||||
|
||||
const dragged = this._draggedCard;
|
||||
if (!dragged) {
|
||||
|
||||
@@ -173,28 +173,35 @@
|
||||
overflow-y: auto;
|
||||
padding: $fp-space-3;
|
||||
flex: 1;
|
||||
transition: background-color $fp-dur $fp-ease;
|
||||
transition: background-color $fp-dur $fp-ease,
|
||||
box-shadow $fp-dur $fp-ease;
|
||||
|
||||
// Drop zone highlight — solid tinted background + dashed outline
|
||||
// so the manager sees EXACTLY where the card will land.
|
||||
// Drop-zone highlight: soft accent tint + inset outline. Shows the
|
||||
// whole column is receptive. The actual insertion point is drawn
|
||||
// by the real placeholder node (inserted by JS) between cards.
|
||||
&.o_fp_drop_target {
|
||||
background-color: color-mix(in srgb, #{$fp-accent} 8%, transparent);
|
||||
box-shadow: inset 0 0 0 2px color-mix(in srgb, #{$fp-accent} 45%, transparent);
|
||||
|
||||
// Placeholder bar at the bottom of the list — that's where
|
||||
// the card will drop when released.
|
||||
&::after {
|
||||
content: "";
|
||||
display: block;
|
||||
height: 56px;
|
||||
margin-top: $fp-space-2;
|
||||
border: 2px dashed color-mix(in srgb, #{$fp-accent} 55%, transparent);
|
||||
border-radius: $fp-radius-md;
|
||||
background-color: color-mix(in srgb, #{$fp-accent} 6%, transparent);
|
||||
}
|
||||
background-color: color-mix(in srgb, #{$fp-accent} 6%, transparent);
|
||||
box-shadow: inset 0 0 0 2px color-mix(in srgb, #{$fp-accent} 40%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
// Insertion placeholder — a live DOM node inserted between cards as
|
||||
// the cursor moves so the manager sees exactly where the drop will
|
||||
// slot in. 4px solid accent bar + small glow + smooth slide.
|
||||
.o_fp_po_drop_placeholder {
|
||||
height: 4px;
|
||||
margin: $fp-space-2 0;
|
||||
background-color: $fp-accent;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, #{$fp-accent} 25%, transparent),
|
||||
0 2px 8px color-mix(in srgb, #{$fp-accent} 35%, transparent);
|
||||
animation: o_fp_placeholder_pulse 1s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes o_fp_placeholder_pulse {
|
||||
from { opacity: 0.75; transform: scaleY(1); }
|
||||
to { opacity: 1; transform: scaleY(1.4); }
|
||||
}
|
||||
|
||||
.o_fp_po_no_cards {
|
||||
padding: $fp-space-5 $fp-space-3;
|
||||
text-align: center;
|
||||
@@ -256,6 +263,21 @@
|
||||
display: flex; align-items: center; gap: $fp-space-2;
|
||||
margin-bottom: $fp-space-1;
|
||||
|
||||
// Small customer avatar — 32px thumbnail. Just identifies the
|
||||
// customer at a glance; not a billboard.
|
||||
.o_fp_po_card_avatar {
|
||||
flex-shrink: 0;
|
||||
width: 32px; height: 32px;
|
||||
border-radius: $fp-radius-sm;
|
||||
object-fit: contain;
|
||||
background-color: $fp-card-soft;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
padding: 2px;
|
||||
}
|
||||
.o_fp_po_card_avatar_blank {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
color: $fp-ink-mute; font-size: 0.95rem;
|
||||
}
|
||||
.o_fp_po_card_title {
|
||||
flex: 1; min-width: 0;
|
||||
font-size: $fp-text-base;
|
||||
|
||||
@@ -92,9 +92,16 @@
|
||||
t-on-dragend="(ev) => this.onCardDragEnd(ev)"
|
||||
t-on-click="() => this.onCardClick(card)">
|
||||
|
||||
<!-- Top row: customer name + step badge
|
||||
(logo removed — it was too dominant) -->
|
||||
<!-- Top row: small customer avatar + name + step pill -->
|
||||
<div class="o_fp_po_card_top">
|
||||
<img t-if="card.customer_logo_url"
|
||||
t-att-src="card.customer_logo_url"
|
||||
class="o_fp_po_card_avatar"
|
||||
alt=""/>
|
||||
<div class="o_fp_po_card_avatar o_fp_po_card_avatar_blank"
|
||||
t-else="">
|
||||
<i class="fa fa-building"/>
|
||||
</div>
|
||||
<div class="o_fp_po_card_title">
|
||||
<strong t-esc="card.customer_name || 'Walk-In'"/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user