This commit is contained in:
Life
2026-03-22 20:40:05 +01:00
parent 02d8e5c2dd
commit da791cd0d0
5 changed files with 411 additions and 414 deletions
@@ -1,5 +1,7 @@
import { FC, useCallback, useEffect, useState } from 'react';
import { Button, Column, Flex, Text } from '../../../common';
import { FaSearch } from 'react-icons/fa';
import { Column, Text } from '../../../common';
import { LayoutFurniIconImageView } from '../../../common/layout/LayoutFurniIconImageView';
import { FurniItem } from '../../../hooks/furni-editor';
interface FurniEditorSearchViewProps
@@ -12,6 +14,8 @@ interface FurniEditorSearchViewProps
onSelect: (id: number) => void;
}
const inputClass = 'text-[14px] border border-[#c5cdd6] rounded px-2 py-1.5 bg-white focus:outline-none focus:border-[#1e7295] transition-colors w-full';
export const FurniEditorSearchView: FC<FurniEditorSearchViewProps> = props =>
{
const { items, total, page, loading, onSearch, onSelect } = props;
@@ -37,97 +41,122 @@ export const FurniEditorSearchView: FC<FurniEditorSearchViewProps> = props =>
return (
<Column gap={ 1 } className="h-full">
<Flex gap={ 1 } alignItems="end">
<Column gap={ 0 } className="flex-1">
<Text small bold>Search</Text>
{ /* Search Bar */ }
<div className="flex gap-2 items-end">
<div className="flex-1">
<label className="text-[12px] text-[#1e7295] uppercase font-bold mb-0.5 block">Search</label>
<input
type="text"
className="form-control form-control-sm"
className={ inputClass }
placeholder="ID, name or sprite ID..."
value={ query }
onChange={ e => setQuery(e.target.value) }
onKeyDown={ handleKeyDown }
/>
</Column>
<Column gap={ 0 } className="w-[80px]">
<Text small bold>Type</Text>
<select
className="form-select form-select-sm"
value={ typeFilter }
onChange={ e => setTypeFilter(e.target.value) }
>
</div>
<div className="w-[100px]">
<label className="text-[12px] text-[#1e7295] uppercase font-bold mb-0.5 block">Type</label>
<select className={ inputClass } value={ typeFilter } onChange={ e => setTypeFilter(e.target.value) }>
<option value="">All</option>
<option value="s">Floor (s)</option>
<option value="i">Wall (i)</option>
<option value="s">Floor</option>
<option value="i">Wall</option>
</select>
</Column>
<Button variant="primary" disabled={ loading } onClick={ handleSearch }>
{ loading ? '...' : 'Search' }
</Button>
</Flex>
</div>
<button
className="flex items-center gap-1.5 px-4 py-1.5 rounded text-[13px] font-bold bg-[#1e7295] text-white hover:bg-[#185d79] transition-colors cursor-pointer disabled:opacity-50"
disabled={ loading }
onClick={ handleSearch }
>
<FaSearch className="text-[11px]" /> { loading ? '...' : 'Search' }
</button>
</div>
<Column gap={ 0 } className="flex-1 overflow-auto border border-[#ccc] rounded bg-white">
<table className="w-full text-xs">
<thead>
<tr className="bg-[#e8e8e8] sticky top-0">
<th className="px-2 py-1 text-left">ID</th>
<th className="px-2 py-1 text-left">Sprite</th>
<th className="px-2 py-1 text-left">Name</th>
<th className="px-2 py-1 text-left">Public Name</th>
<th className="px-2 py-1 text-center">Type</th>
<th className="px-2 py-1 text-left">Interaction</th>
</tr>
</thead>
<tbody>
{ items.map(item => (
<tr
key={ item.id }
className="cursor-pointer hover:bg-[#d4edfa] border-b border-[#eee] transition-colors"
onClick={ () => onSelect(item.id) }
>
<td className="px-2 py-1 font-mono">{ item.id }</td>
<td className="px-2 py-1 font-mono">{ item.spriteId }</td>
<td className="px-2 py-1 truncate max-w-[120px]">{ item.itemName }</td>
<td className="px-2 py-1 truncate max-w-[120px]">{ item.publicName }</td>
<td className="px-2 py-1 text-center">
<span className={ `px-1 rounded text-white text-[10px] ${ item.type === 's' ? 'bg-[#1e7295]' : 'bg-[#6b7280]' }` }>
{ item.type === 's' ? 'Floor' : 'Wall' }
</span>
</td>
<td className="px-2 py-1 text-[10px]">{ item.interactionType || '-' }</td>
</tr>
)) }
{ items.length === 0 && !loading &&
<tr>
<td colSpan={ 6 } className="px-2 py-4 text-center text-[#999]">No items found</td>
</tr>
}
</tbody>
</table>
</Column>
{ /* Results counter */ }
{ total > 0 &&
<div className="text-[13px] text-[#4a5568]">
<b className="text-[#1e7295]">{ total }</b> items found { totalPages > 1 && <span>- Page <b>{ page }</b>/{ totalPages }</span> }
</div>
}
{ /* Results Table */ }
<div className="flex-1 overflow-auto border border-[#c5cdd6] rounded bg-white">
{ loading &&
<div className="flex items-center justify-center py-8">
<div className="text-[14px] text-[#4a5568] animate-pulse">Loading...</div>
</div>
}
{ !loading && items.length === 0 &&
<div className="flex items-center justify-center py-8 text-[14px] text-[#4a5568]">
No items found
</div>
}
{ !loading && items.length > 0 &&
<table className="w-full text-[14px]">
<thead>
<tr className="bg-[#f0f4f7] sticky top-0 text-[12px] text-[#1e7295] uppercase font-bold">
<th className="px-2 py-2 text-center w-[44px]"></th>
<th className="px-2 py-2 text-left w-[55px]">ID</th>
<th className="px-2 py-2 text-left w-[60px]">Sprite</th>
<th className="px-2 py-2 text-left">Name</th>
<th className="px-2 py-2 text-left">Public Name</th>
<th className="px-2 py-2 text-center w-[60px]">Type</th>
<th className="px-2 py-2 text-left">Interaction</th>
</tr>
</thead>
<tbody>
{ items.map(item => (
<tr
key={ item.id }
className="cursor-pointer hover:bg-[#e8f4fb] border-b border-[#f0f0f0] transition-colors"
onClick={ () => onSelect(item.id) }
>
<td className="px-2 py-1 text-center">
<div className="w-[34px] h-[34px] flex items-center justify-center mx-auto">
<LayoutFurniIconImageView productType={ item.type } productClassId={ item.spriteId } />
</div>
</td>
<td className="px-2 py-1 font-mono text-[14px] text-[#1e7295] font-bold">{ item.id }</td>
<td className="px-2 py-1 font-mono text-[14px] text-[#4a5568]">{ item.spriteId }</td>
<td className="px-2 py-1 text-[14px] text-[#2d3748] truncate max-w-[130px]">{ item.itemName }</td>
<td className="px-2 py-1 text-[14px] text-[#2d3748] truncate max-w-[130px]">{ item.publicName }</td>
<td className="px-2 py-1 text-center">
<span className={ `px-2 py-0.5 rounded text-white text-[11px] font-bold ${ item.type === 's' ? 'bg-[#1e7295]' : 'bg-[#718096]' }` }>
{ item.type === 's' ? 'Floor' : 'Wall' }
</span>
</td>
<td className="px-2 py-1 text-[14px] text-[#4a5568]">{ item.interactionType || '-' }</td>
</tr>
)) }
</tbody>
</table>
}
</div>
{ /* Pagination */ }
{ totalPages > 1 &&
<Flex gap={ 1 } justifyContent="between" alignItems="center">
<Text small variant="gray">
{ total } items - Page { page }/{ totalPages }
</Text>
<Flex gap={ 1 }>
<Button
variant="secondary"
<div className="flex justify-between items-center">
<div className="text-[13px] text-[#4a5568]">
Page <b>{ page }</b> of <b>{ totalPages }</b>
</div>
<div className="flex gap-1.5">
<button
className="px-3 py-1.5 rounded text-[13px] font-bold bg-[#edf2f7] hover:bg-[#e2e8f0] text-[#4a5568] transition-colors cursor-pointer disabled:opacity-40"
disabled={ page <= 1 }
onClick={ () => onSearch(query, typeFilter, page - 1) }
>
Prev
</Button>
<Button
variant="secondary"
</button>
<button
className="px-3 py-1.5 rounded text-[13px] font-bold bg-[#edf2f7] hover:bg-[#e2e8f0] text-[#4a5568] transition-colors cursor-pointer disabled:opacity-40"
disabled={ page >= totalPages }
onClick={ () => onSearch(query, typeFilter, page + 1) }
>
Next
</Button>
</Flex>
</Flex>
</button>
</div>
</div>
}
</Column>
);