feat: extract floor/building/room/trailer from asset names
Adds scripts/extract_asset_location_data.py which: - Extracts floor, building_number, room, trailer_number, building_name from names - Detects and removes duplicated name segments - Updates both assets (1848) and locations (364) tables - Dry-run mode by default, --apply to write changes
This commit is contained in:
Binary file not shown.
@@ -0,0 +1 @@
|
||||
[{"machine_id":"68002","lat":28.388656,"lng":-81.535256},{"machine_id":"68097","lat":28.425122,"lng":-81.57718},{"machine_id":"68194","lat":28.458878,"lng":-81.452836},{"machine_id":"68356","lat":28.346058,"lng":-81.588889},{"machine_id":"68363","lat":28.316533,"lng":-81.405814},{"machine_id":"68551","lat":28.458892,"lng":-81.452867},{"machine_id":"69150","lat":28.431561,"lng":-81.470789},{"machine_id":"69331","lat":28.315697,"lng":-81.405889},{"machine_id":"69332","lat":28.316533,"lng":-81.405814},{"machine_id":"71440","lat":28.369956,"lng":-81.552925},{"machine_id":"71504","lat":28.458906,"lng":-81.452881},{"machine_id":"71574","lat":28.371833,"lng":-81.514336},{"machine_id":"73016","lat":28.425367,"lng":-81.458389},{"machine_id":"93771","lat":28.338208,"lng":-81.571883},{"machine_id":"95020","lat":28.337258,"lng":-81.5729},{"machine_id":"95054","lat":28.347808,"lng":-81.541811},{"machine_id":"95067","lat":28.350125,"lng":-81.544425},{"machine_id":"95100","lat":28.339014,"lng":-81.553969},{"machine_id":"95167","lat":28.385514,"lng":-81.534722},{"machine_id":"95173","lat":28.388247,"lng":-81.534814},{"machine_id":"95330","lat":28.339564,"lng":-81.576981},{"machine_id":"95418","lat":28.458892,"lng":-81.452867},{"machine_id":"97115","lat":28.415494,"lng":-81.413122},{"machine_id":"98059","lat":28.336683,"lng":-81.587358},{"machine_id":"98072","lat":28.3713,"lng":-81.529883},{"machine_id":"98114","lat":28.406067,"lng":-81.582122},{"machine_id":"98164","lat":29.215369,"lng":-81.094622},{"machine_id":"98176","lat":29.216575,"lng":-81.100233},{"machine_id":"98184","lat":29.215369,"lng":-81.094622}]
|
||||
@@ -0,0 +1,437 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Extract floor, building_number, room, trailer_number, and building_name
|
||||
from asset names in assets.db. Also deduplicate duplicated name segments.
|
||||
|
||||
Usage:
|
||||
python3 scripts/extract_asset_location_data.py # dry-run (report only)
|
||||
python3 scripts/extract_asset_location_data.py --apply # update DB
|
||||
"""
|
||||
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
DB_PATH = str(Path(__file__).resolve().parent.parent / "assets.db")
|
||||
|
||||
|
||||
# ─── Regex patterns ──────────────────────────────────────────────────────────
|
||||
|
||||
# Floor: "1ST FL", "2nd Floor", "3rd Floor", "4TH FLO", "5th floor", "6TH FL",
|
||||
# "SOG MAIN 3RD FLOOR", "7th floor", "G-REAR 6TH FL", etc.
|
||||
# We capture the floor number as the first group.
|
||||
FLOOR_PATTERNS = [
|
||||
re.compile(r'\b(\d+)(?:ST|ND|RD|TH|st|nd|rd|th)\s+(?:FL(?:OOR)?|Floor|floor|FLO(?:OR)?|Floo|floo)\b', re.IGNORECASE),
|
||||
# "2ND FLOOR", "3RD FLOOR" etc.
|
||||
re.compile(r'\b(\d+)(?:TH|ST|ND|RD)\s+FLOOR\b', re.IGNORECASE),
|
||||
# "3rd FLR" (FLR abbreviation)
|
||||
re.compile(r'\b(\d+)(?:ST|ND|RD|TH|st|nd|rd|th)\s+FLR\b', re.IGNORECASE),
|
||||
]
|
||||
|
||||
# Floor: "FL02" in "FL02NEWBLD" -> floor "2"
|
||||
# Must be FL + 1-2 digits + 2+ uppercase letters or end of field (via word boundary).
|
||||
# Excludes "FL4950", "FL2500", "FL8108", "FL13323" (4+ digits after FL).
|
||||
# Also matches "FL10W" (floor 10 west), "FL24TWR" (floor 24 tower).
|
||||
FLOOR_CODE = re.compile(r'\bFL(\d{1,2})(?:(?:[A-Z]{2,})|\b)')
|
||||
|
||||
# Building-number patterns
|
||||
# "BLDG 215", "Bldg 210", "Building 1180", "BLDG 220", "Bldg 215-3rd Floor"
|
||||
BUILDING_PATTERNS = [
|
||||
re.compile(r'\b(?:BLDG|Bldg|Building|Bldg)\s+(\d+)\b'),
|
||||
# "#6328", "#811", "#9842", "#5288", "#1001", "# 6328" (store/chain numbers)
|
||||
re.compile(r'#\s*(\d{3,})\b'),
|
||||
]
|
||||
|
||||
# Room patterns
|
||||
# "Room 1290", "Room 1323", "SUITE 100", "SUITE 214", "Ste 400", "Ste 108"
|
||||
ROOM_PATTERNS = [
|
||||
re.compile(r'\b(?:Room|room)\s+(\d+)\b'),
|
||||
re.compile(r'\b(?:SUITE|Suite|suite|Ste)\s+(\d+)\b'),
|
||||
# "Breakroom suite 700" (suite at end)
|
||||
re.compile(r'\bsuite\s+(\d+)\b'),
|
||||
]
|
||||
|
||||
# Trailer patterns
|
||||
# "Trailer 1051", "Trailer 1135", "Trailer 1055", "Trailer 1011", "Trailer 1145", "Trailer 1155"
|
||||
# Also "Trailer 342" (3-digit trailer)
|
||||
TRAILER_PATTERNS = [
|
||||
re.compile(r'\b(?:Trailer|trailer)\s+(\d+)\b'),
|
||||
]
|
||||
|
||||
|
||||
# ─── Known building/venue names (prefixes to check) ──────────────────────────
|
||||
# These appear as prefix segments in asset names and are valid building names.
|
||||
BUILDING_NAME_PREFIXES = [
|
||||
"Buena Vista Palace",
|
||||
"Hilton Worldwide",
|
||||
"Four Seasons Resort",
|
||||
"Gran Destino",
|
||||
"Shades of Green",
|
||||
"Coronado Springs",
|
||||
"Caribbean Beach",
|
||||
"All Star Movies",
|
||||
"Disney Springs",
|
||||
"Epcot",
|
||||
"Celebration Suites",
|
||||
]
|
||||
|
||||
# Suffixes that indicate what follows is a function/area, NOT part of building name.
|
||||
AREA_SUFFIXES = [
|
||||
"Breakroom", "Break Room", "BRKRM", "Break", "BR",
|
||||
"Vending", "Vending Area",
|
||||
"Laundry", "Laundry Room",
|
||||
"Cafeteria", "Cafe",
|
||||
"Maintenance", "Warehouse",
|
||||
"Wardrobe", "Cast",
|
||||
"Lobby", "Guest",
|
||||
"Admin", "Office",
|
||||
]
|
||||
|
||||
|
||||
def extract_floor(name: str) -> Optional[str]:
|
||||
"""Extract floor number from an asset name."""
|
||||
for pat in FLOOR_PATTERNS:
|
||||
m = pat.search(name)
|
||||
if m:
|
||||
return str(int(m.group(1))) # Normalise "02" -> "2"
|
||||
m = FLOOR_CODE.search(name)
|
||||
if m:
|
||||
val = str(int(m.group(1))) # Normalise "02" -> "2"
|
||||
# Ensure it's a reasonable floor number (1-100)
|
||||
try:
|
||||
num = int(val)
|
||||
if 1 <= num <= 100:
|
||||
return val
|
||||
except ValueError:
|
||||
pass
|
||||
# Edge: "Villas 5 fl by room" etc. — rare but present
|
||||
m = re.search(r'\b(\d+)\s+fl\b', name, re.IGNORECASE)
|
||||
if m:
|
||||
val = str(int(m.group(1)))
|
||||
try:
|
||||
if 1 <= int(val) <= 100:
|
||||
return val
|
||||
except ValueError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def extract_building_number(name: str) -> Optional[str]:
|
||||
"""Extract building number from an asset name."""
|
||||
for pat in BUILDING_PATTERNS:
|
||||
m = pat.search(name)
|
||||
if m:
|
||||
return m.group(1)
|
||||
return None
|
||||
|
||||
|
||||
def extract_room(name: str) -> Optional[str]:
|
||||
"""Extract room/suite number from an asset name."""
|
||||
for pat in ROOM_PATTERNS:
|
||||
m = pat.search(name)
|
||||
if m:
|
||||
return m.group(1)
|
||||
return None
|
||||
|
||||
|
||||
def extract_trailer(name: str) -> Optional[str]:
|
||||
"""Extract trailer number from an asset name."""
|
||||
for pat in TRAILER_PATTERNS:
|
||||
m = pat.search(name)
|
||||
if m:
|
||||
return m.group(1)
|
||||
return None
|
||||
|
||||
|
||||
def extract_building_name(name: str) -> Optional[str]:
|
||||
"""
|
||||
Attempt to derive a building/venue name from the asset name.
|
||||
|
||||
Strategy: look for known building-name prefixes, or extract the
|
||||
first segment before a hyphen/separator that looks like a location name
|
||||
(not just a person name, address fragment, or functional descriptor).
|
||||
"""
|
||||
name_upper = name.upper()
|
||||
|
||||
# Check known building name prefixes
|
||||
for prefix in BUILDING_NAME_PREFIXES:
|
||||
if prefix.upper() in name_upper:
|
||||
return prefix
|
||||
|
||||
# Try prefix extraction: the first significant segment
|
||||
# Split on common separators
|
||||
parts = re.split(r'\s*-\s*', name)
|
||||
first = parts[0].strip()
|
||||
|
||||
# Skip if the first segment looks like a person name (has name pattern)
|
||||
# e.g. "Todd J - ...", "Brenda G - ...", "Kelli M - ..."
|
||||
person_pattern = re.match(r'^[A-Z][a-z]+ [A-Z]\.?$', first)
|
||||
if person_pattern:
|
||||
return None
|
||||
|
||||
# Skip if it's an address fragment
|
||||
if re.match(r'^\d+\s+', first):
|
||||
# Could be a building name like "Building 1180" — but that's already
|
||||
# captured as building_number. Skip other addresses.
|
||||
if not re.match(r'^Building\s+\d+', first, re.IGNORECASE):
|
||||
return None
|
||||
|
||||
# Skip if the first segment is clearly a person or Disney cast ID
|
||||
# "Jeremy B", "Randal W", "Susan G", "Brenda G", etc.
|
||||
if re.match(r'^[A-Z][a-z]+ [A-Z] -', first):
|
||||
return None
|
||||
|
||||
# If first segment is long enough (>10 chars) and doesn't contain area keywords,
|
||||
# it's probably the building/venue name
|
||||
first_no_area = first
|
||||
for suffix in AREA_SUFFIXES:
|
||||
# Remove trailing area/function suffixes
|
||||
pat = re.compile(r'\s+' + re.escape(suffix) + r'$', re.IGNORECASE)
|
||||
first_no_area = pat.sub('', first_no_area)
|
||||
|
||||
if len(first_no_area) > 8 and not re.search(r'\b(?:BREAK|VEND|LAUNDRY|CAFE|MAINT|WAREHOUSE|CAFETERIA|OFFICE|LOBBY|GUEST|ADMIN|SERVICES|BACKSTAGE|WARDROBE|CA$T|DINING|KITCHEN|STORAGE|WORKSHOP|SECURITY|RECEIVING|PARKING|OUTSIDE|OUTLET|HALLWAY)\b', first_no_area.upper()):
|
||||
return first_no_area.strip()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# ─── Name deduplication ──────────────────────────────────────────────────────
|
||||
|
||||
def should_deduplicate(left: str, right: str) -> Optional[str]:
|
||||
"""
|
||||
Check if `right` is a duplicate/continuation of `left`.
|
||||
Returns the merged name if deduplication is warranted, None otherwise.
|
||||
"""
|
||||
l = left.strip()
|
||||
r = right.strip()
|
||||
if not l or not r:
|
||||
return None
|
||||
|
||||
# Exact match: "X-X" -> keep "X"
|
||||
if l.lower() == r.lower():
|
||||
return l
|
||||
|
||||
# Right starts with left: "X" + "-" + "X Y" -> "X Y"
|
||||
if r.lower().startswith(l.lower()):
|
||||
return r
|
||||
|
||||
# Left starts with right: "X Y" + "-" + "X" -> "X Y"
|
||||
if l.lower().startswith(r.lower()):
|
||||
return l
|
||||
|
||||
# Large common prefix (>= 65 % of the shorter string and >= 10 chars)
|
||||
min_len = min(len(l), len(r))
|
||||
if min_len >= 10:
|
||||
common = 0
|
||||
for a, b in zip(l.lower(), r.lower()):
|
||||
if a == b:
|
||||
common += 1
|
||||
else:
|
||||
break
|
||||
if common >= 0.65 * min_len:
|
||||
# Keep the longer variant
|
||||
return l if len(l) >= len(r) else r
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def deduplicate_name(name: str) -> str:
|
||||
"""
|
||||
Detect duplicated name segments and return a cleaned name.
|
||||
|
||||
Strategy: for assets with multiple hyphen-separated segments, check
|
||||
if splitting at each hyphen position reveals duplication.
|
||||
"""
|
||||
# Find all hyphen positions and try each
|
||||
hyphens = [m.start() for m in re.finditer('-', name)]
|
||||
|
||||
best_name = name
|
||||
|
||||
# Try splitting at first hyphen
|
||||
for idx in hyphens:
|
||||
left = name[:idx].strip()
|
||||
right = name[idx+1:].strip()
|
||||
|
||||
result = should_deduplicate(left, right)
|
||||
if result is not None:
|
||||
candidate = result
|
||||
# If this is shorter or the same length but cleaner, use it
|
||||
if len(candidate) < len(best_name) or (len(candidate) <= len(best_name) and candidate != name):
|
||||
best_name = candidate
|
||||
|
||||
# Iterative: run again on the result (for triple-nested duplicates)
|
||||
if best_name != name:
|
||||
best_name = deduplicate_name(best_name)
|
||||
|
||||
return best_name
|
||||
|
||||
|
||||
# ─── Main logic ──────────────────────────────────────────────────────────────
|
||||
|
||||
def process_assets(db: sqlite3.Connection, apply: bool = False) -> dict:
|
||||
"""Process assets table, returning counts of extracted values."""
|
||||
cursor = db.cursor()
|
||||
|
||||
# Fetch all assets with non-empty names
|
||||
cursor.execute("SELECT id, name, floor, building_number, room, trailer_number, building_name FROM assets WHERE name != '' ORDER BY id")
|
||||
rows = cursor.fetchall()
|
||||
|
||||
counts = {
|
||||
'floor': 0,
|
||||
'building_number': 0,
|
||||
'room': 0,
|
||||
'trailer_number': 0,
|
||||
'building_name': 0,
|
||||
'name_cleaned': 0,
|
||||
'total': len(rows),
|
||||
}
|
||||
|
||||
updates = []
|
||||
|
||||
for row in rows:
|
||||
asset_id, name, cur_floor, cur_bldg_num, cur_room, cur_trailer, cur_bldg_name = row
|
||||
|
||||
# 1. Deduplicate name
|
||||
cleaned = deduplicate_name(name)
|
||||
name_changed = (cleaned != name)
|
||||
|
||||
# 2. Extract fields (only if currently empty)
|
||||
floor = cur_floor if cur_floor else (extract_floor(cleaned) or '')
|
||||
building_number = cur_bldg_num if cur_bldg_num else (extract_building_number(cleaned) or '')
|
||||
room = cur_room if cur_room else (extract_room(cleaned) or '')
|
||||
trailer = cur_trailer if cur_trailer else (extract_trailer(cleaned) or '')
|
||||
building_name = cur_bldg_name if cur_bldg_name else (extract_building_name(cleaned) or '')
|
||||
|
||||
# Track counts
|
||||
if floor and not cur_floor:
|
||||
counts['floor'] += 1
|
||||
if building_number and not cur_bldg_num:
|
||||
counts['building_number'] += 1
|
||||
if room and not cur_room:
|
||||
counts['room'] += 1
|
||||
if trailer and not cur_trailer:
|
||||
counts['trailer_number'] += 1
|
||||
if building_name and not cur_bldg_name:
|
||||
counts['building_name'] += 1
|
||||
if name_changed:
|
||||
counts['name_cleaned'] += 1
|
||||
|
||||
updates.append((floor, building_number, room, trailer, building_name, cleaned, asset_id))
|
||||
|
||||
if apply:
|
||||
cursor.executemany(
|
||||
"UPDATE assets SET floor=?, building_number=?, room=?, trailer_number=?, building_name=?, name=? WHERE id=?",
|
||||
updates
|
||||
)
|
||||
db.commit()
|
||||
|
||||
return counts
|
||||
|
||||
|
||||
def process_locations(db: sqlite3.Connection, apply: bool = False) -> dict:
|
||||
"""Process locations table similarly."""
|
||||
cursor = db.cursor()
|
||||
cursor.execute("SELECT id, name, floor, building_number, trailer_number FROM locations WHERE name != '' ORDER BY id")
|
||||
rows = cursor.fetchall()
|
||||
|
||||
counts = {
|
||||
'floor': 0,
|
||||
'building_number': 0,
|
||||
'trailer_number': 0,
|
||||
'name_cleaned': 0,
|
||||
'total': len(rows),
|
||||
}
|
||||
|
||||
updates = []
|
||||
|
||||
for row in rows:
|
||||
loc_id, name, cur_floor, cur_bldg_num, cur_trailer = row
|
||||
|
||||
# Deduplicate (locations seem cleaner but process anyway)
|
||||
cleaned = deduplicate_name(name)
|
||||
name_changed = (cleaned != name)
|
||||
|
||||
# Extract fields
|
||||
floor = cur_floor if cur_floor else (extract_floor(cleaned) or '')
|
||||
building_number = cur_bldg_num if cur_bldg_num else (extract_building_number(cleaned) or '')
|
||||
trailer = cur_trailer if cur_trailer else (extract_trailer(cleaned) or '')
|
||||
|
||||
if floor and not cur_floor:
|
||||
counts['floor'] += 1
|
||||
if building_number and not cur_bldg_num:
|
||||
counts['building_number'] += 1
|
||||
if trailer and not cur_trailer:
|
||||
counts['trailer_number'] += 1
|
||||
if name_changed:
|
||||
counts['name_cleaned'] += 1
|
||||
|
||||
updates.append((floor, building_number, trailer, cleaned, loc_id))
|
||||
|
||||
if apply:
|
||||
cursor.executemany(
|
||||
"UPDATE locations SET floor=?, building_number=?, trailer_number=?, name=? WHERE id=?",
|
||||
updates
|
||||
)
|
||||
db.commit()
|
||||
|
||||
return counts
|
||||
|
||||
|
||||
def print_summary(label: str, counts: dict):
|
||||
"""Print a human-readable summary of extraction results."""
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f" {label}")
|
||||
print(f"{'=' * 60}")
|
||||
print(f" Total rows processed: {counts['total']}")
|
||||
print(f" Floors extracted: {counts['floor']}")
|
||||
print(f" Building numbers: {counts['building_number']}")
|
||||
if 'room' in counts:
|
||||
print(f" Rooms/Suites: {counts['room']}")
|
||||
if 'trailer_number' in counts:
|
||||
print(f" Trailer numbers: {counts['trailer_number']}")
|
||||
if 'building_name' in counts:
|
||||
print(f" Building names: {counts['building_name']}")
|
||||
print(f" Names cleaned: {counts['name_cleaned']}")
|
||||
|
||||
|
||||
def main():
|
||||
apply = '--apply' in sys.argv
|
||||
|
||||
print(f"DB: {DB_PATH}")
|
||||
print(f"Mode: {'APPLY (will write to DB)' if apply else 'DRY RUN (no changes)'}")
|
||||
|
||||
db = sqlite3.connect(DB_PATH)
|
||||
db.execute("PRAGMA journal_mode=WAL")
|
||||
|
||||
print("\nProcessing assets...")
|
||||
asset_counts = process_assets(db, apply=apply)
|
||||
print_summary("ASSETS", asset_counts)
|
||||
|
||||
print("\nProcessing locations...")
|
||||
loc_counts = process_locations(db, apply=apply)
|
||||
print_summary("LOCATIONS", loc_counts)
|
||||
|
||||
db.close()
|
||||
|
||||
if not apply:
|
||||
print("\n─── DRY RUN ───")
|
||||
print("Re-run with --apply to write changes to the database.")
|
||||
|
||||
# Sample some cleaned names for verification
|
||||
print("\n─── Sample deduplications (first 20) ───")
|
||||
db2 = sqlite3.connect(DB_PATH)
|
||||
cursor = db2.cursor()
|
||||
cursor.execute("SELECT id, name FROM assets WHERE name LIKE '%-%' ORDER BY id LIMIT 20")
|
||||
for row in cursor.fetchall():
|
||||
original = row[1]
|
||||
cleaned = deduplicate_name(original)
|
||||
if cleaned != original:
|
||||
print(f" [{row[0]}] '{original}'")
|
||||
print(f" → '{cleaned}'")
|
||||
db2.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
+9
-6
@@ -925,7 +925,7 @@
|
||||
<div class="card-title">Create New Asset</div>
|
||||
<input id="newMachineId" type="text" class="input-field" placeholder="Machine ID" readonly>
|
||||
<input id="newName" type="text" class="input-field" placeholder="Asset name *" required>
|
||||
<textarea id="newDesc" class="input-field" placeholder="Description (optional)" rows="2"></textarea>
|
||||
<textarea id="newDesc" class="input-field" placeholder="Telemetry ID (optional)" rows="2"></textarea>
|
||||
<div class="form-row" style="margin-bottom:8px;">
|
||||
<select id="newCatSelect" class="input-field">
|
||||
<option value="">Category</option>
|
||||
@@ -1003,7 +1003,7 @@
|
||||
<input id="manMachineId" type="text" class="input-field" placeholder="Machine ID *" required>
|
||||
<input id="manSerialNumber" type="text" class="input-field" placeholder="Serial Number">
|
||||
<input id="manName" type="text" class="input-field" placeholder="Asset Name *" required>
|
||||
<textarea id="manDescription" class="input-field" placeholder="Description" rows="2"></textarea>
|
||||
<textarea id="manDescription" class="input-field" placeholder="Telemetry ID" rows="2"></textarea>
|
||||
|
||||
<div class="form-row" style="margin-bottom:8px;">
|
||||
<select id="manCatSelect" class="input-field">
|
||||
@@ -1129,7 +1129,7 @@
|
||||
<!-- ── List View ────────────────────────────────────────────────────── -->
|
||||
<div id="assetsListView">
|
||||
<div class="search-bar">
|
||||
<input id="assetSearch" type="text" class="input-field" placeholder="Search name, machine ID, serial, description..." oninput="loadAssets()">
|
||||
<input id="assetSearch" type="text" class="input-field" placeholder="Search name, machine ID, serial, telemetry ID..." oninput="loadAssets()">
|
||||
<button class="clear-btn" id="clearSearch" onclick="clearAssetSearch()">✕</button>
|
||||
</div>
|
||||
<div style="display:flex;gap:6px;align-items:flex-start;margin-bottom:8px;">
|
||||
@@ -1287,7 +1287,7 @@
|
||||
<div class="card" id="editFormCard">
|
||||
<input id="editBarcode" type="text" class="input-field" placeholder="Machine ID *">
|
||||
<input id="editName" type="text" class="input-field" placeholder="Asset name *">
|
||||
<textarea id="editDesc" class="input-field" placeholder="Description" rows="2"></textarea>
|
||||
<textarea id="editDesc" class="input-field" placeholder="Telemetry ID" rows="2"></textarea>
|
||||
<div class="form-row" style="margin-bottom:8px;">
|
||||
<select id="editCategory" class="input-field">
|
||||
<option value="">Category</option>
|
||||
@@ -3172,6 +3172,9 @@
|
||||
'Food': { icon: '🍔', color: '#d35400' },
|
||||
'Equipment': { icon: '🔧', color: '#2c3e50' },
|
||||
'Furniture': { icon: '🪑', color: '#8e44ad' },
|
||||
'Appliances': { icon: '🔌', color: '#3b82f6' },
|
||||
'Unknown': { icon: '❓', color: '#6b7280' },
|
||||
'GF Bev': { icon: '🥤', color: '#0a7ab5' },
|
||||
};
|
||||
|
||||
const PARK_ICONS = {'magic-kingdom':'🏰','epcot':'🌍','hollywood-studios':'🎬','animal-kingdom':'🌿','disney-springs':'🛍️','resort':'🏨','office':'🏢','other':'📍'};
|
||||
@@ -3809,7 +3812,7 @@
|
||||
df('Machine ID', a.machine_id, true),
|
||||
df('Name', a.name),
|
||||
df('Serial Number', a.serial_number, true),
|
||||
df('Description', a.description),
|
||||
df('Telemetry ID', a.description),
|
||||
df('Category', catLabel(a.category)),
|
||||
df('Make', a.make),
|
||||
df('Model', a.model),
|
||||
@@ -4194,7 +4197,7 @@
|
||||
<input id="editMachineId" type="text" class="input-field" placeholder="Machine ID *" value="${esc(a.machine_id)}">
|
||||
<input id="editName" type="text" class="input-field" placeholder="Asset name *" value="${esc(a.name)}">
|
||||
<input id="editSerialNumber" type="text" class="input-field" placeholder="Serial number" value="${esc(a.serial_number || '')}">
|
||||
<textarea id="editDesc" class="input-field" placeholder="Description" rows="2">${esc(a.description || '')}</textarea>
|
||||
<textarea id="editDesc" class="input-field" placeholder="Telemetry ID" rows="2">${esc(a.description || '')}</textarea>
|
||||
<div class="form-row">
|
||||
<input id="editCategory" type="text" class="input-field" placeholder="Category" value="${esc(a.category || '')}" list="catList">
|
||||
<input id="editMake" type="text" class="input-field" placeholder="Make" value="${esc(a.make || '')}">
|
||||
|
||||
Reference in New Issue
Block a user