diff --git a/app/controllers/tools_controller.rb b/app/controllers/tools_controller.rb index 07c89f7c..82127c47 100644 --- a/app/controllers/tools_controller.rb +++ b/app/controllers/tools_controller.rb @@ -7,16 +7,7 @@ class ToolsController < ApplicationController # The tools table gets data from here through AJAX, so we keep # compute last_page to tell table how many batches it needs def index - type = params[:type] || 'tool' - - tools = Tool.just_tools - - case type - when 'hardhat' - tools = Tool.hardhats - when 'radio' - tools = Tool.radios - end + tools = tools_for_type(params[:type]) @json_url = url_for(params.permit(:type, :page, :size).merge(format: :json)) @@ -37,6 +28,9 @@ def index .offset(offset) .limit(size) data = tools.table_attrs.as_json(methods: %i[link]) + data.each do |t| + t['image_url'] = helpers.asset_path("tool_images/#{t['t_name']}.png") + end render json: { last_page:, data: } end end @@ -75,6 +69,17 @@ def destroy private + def tools_for_type(type) + case type + when 'hardhat' + Tool.hardhats + when 'radio' + Tool.radios + else + Tool.just_tools + end + end + def tool_params params.expect(tool: %i[name description barcode tool_type_id]) end diff --git a/app/helpers/tools_helper.rb b/app/helpers/tools_helper.rb index bf59f58f..8e8fef4c 100644 --- a/app/helpers/tools_helper.rb +++ b/app/helpers/tools_helper.rb @@ -5,10 +5,7 @@ module ToolsHelper { title: 'Name', field: :t_name, - formatter: 'link', - formatterParams: { - urlField: :link - }, + formatter: 'imageTooltip', frozen: true, headerFilter: 'input', headerFilterPlaceholder: 'Search', diff --git a/app/javascript/controllers/table_controller.js b/app/javascript/controllers/table_controller.js index 3dc06678..ef3c4040 100644 --- a/app/javascript/controllers/table_controller.js +++ b/app/javascript/controllers/table_controller.js @@ -21,7 +21,50 @@ export default class extends Controller { const e = document.createElement("div"); const config = JSON.parse(this.element.dataset.config); + // display tool image config.columns?.forEach((col) => { + if (col.formatter === "imageTooltip") { + col.formatter = (cell) => { + const data = cell.getRow().getData(); + const a = document.createElement("a"); + a.href = data.link || "#"; + a.textContent = cell.getValue(); + + if (data.image_url) { + const tooltip = document.createElement("div"); + tooltip.style.cssText = + "position:fixed;z-index:9999;background:#fff;border:1px solid #ccc;" + + "border-radius:4px;padding:4px;display:none;pointer-events:none;box-shadow:0 2px 8px rgba(0,0,0,.2);"; + const img = document.createElement("img"); + img.src = data.image_url; + img.style.cssText = + "max-width:180px;max-height:180px;display:block;"; + img.onerror = () => { + tooltip.style.display = "none"; + }; + tooltip.appendChild(img); + document.body.appendChild(tooltip); + this._tooltips = this._tooltips || []; + this._tooltips.push(tooltip); + + a.addEventListener("mouseenter", (ev) => { + tooltip.style.display = "block"; + tooltip.style.left = ev.clientX + 12 + "px"; + tooltip.style.top = ev.clientY + 12 + "px"; + }); + a.addEventListener("mousemove", (ev) => { + tooltip.style.left = ev.clientX + 12 + "px"; + tooltip.style.top = ev.clientY + 12 + "px"; + }); + a.addEventListener("mouseleave", () => { + tooltip.style.display = "none"; + }); + } + + return a; + }; + } + if (col.field === "due_at_countdown") { col.formatter = (cell) => { const el = cell.getElement(); @@ -76,6 +119,8 @@ export default class extends Controller { } disconnect() { + this._tooltips?.forEach((t) => t.remove()); + this._tooltips = null; clearCountdownIntervalsFromTable(this._table); this._table?.destroy(); this._table = null; diff --git a/db/binder_development.sqlite3-shm b/db/binder_development.sqlite3-shm new file mode 100644 index 00000000..fe9ac284 Binary files /dev/null and b/db/binder_development.sqlite3-shm differ diff --git a/db/binder_development.sqlite3-wal b/db/binder_development.sqlite3-wal new file mode 100644 index 00000000..e69de29b