Skip to content

Commit 0fd2b8f

Browse files
committed
fix(web): identidade estavel em updates otimistas e tratamento de erro em chamadas NUI (#61)
1 parent bd1cd13 commit 0fd2b8f

12 files changed

Lines changed: 56 additions & 36 deletions

File tree

web/src/App.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -269,13 +269,9 @@ export default function App() {
269269
}
270270

271271
if (visible) {
272-
try {
273-
// notify client to hide UI (client may also call SetNuiFocus(false))
274-
if (sendNui) sendNui('hideUI')
275-
} catch {
276-
// ignore
277-
setVisible(false)
278-
}
272+
// notify client to hide UI (client may also call SetNuiFocus(false))
273+
if (sendNui) sendNui('hideUI').catch(() => setVisible(false))
274+
else setVisible(false)
279275
}
280276
}
281277
}

web/src/components/Listeners.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export default function Listeners() {
159159
observer.observe(document.body, { childList: true, subtree: true });
160160

161161
// Request initial data to avoid race conditions
162-
sendNui('getData', {}, { ...MOCK_GAME_DATA, players: MOCK_PLAYERS }).then(onData)
162+
sendNui('getData', {}, { ...MOCK_GAME_DATA, players: MOCK_PLAYERS }).then(onData).catch((e) => console.error('getData failed', e))
163163

164164
return () => {
165165
observer.disconnect();

web/src/components/groups/GroupList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export default function GroupList({
6161
style={{ height: '100%' }}
6262
data={filtered}
6363
itemContent={(index, group) => {
64-
const key = `${type}-${group.name}-${index}`
64+
const key = `${type}-${group.name}`
6565
const isExpanded = expanded[key]
6666

6767
return (

web/src/components/players/InventoryViewerModal.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,12 @@ const InventorySlot = ({ target, onClose, onAddComparison, isSecondary, otherTar
108108

109109
// Register for real-time updates
110110
sendNui('mri_Qadmin:server:StartWatchingInventory', { id: target.id, type: target.type })
111+
.catch((e) => console.error('StartWatchingInventory failed', e))
111112

112113
return () => {
113114
// Unregister when closed
114115
sendNui('mri_Qadmin:server:StopWatchingInventory', { id: target.id, type: target.type })
116+
.catch((e) => console.error('StopWatchingInventory failed', e))
115117
}
116118
}, [target.id, target.type, fetchInventory, sendNui])
117119

web/src/pages/Dashboard/Dashboard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,8 @@ export default function Dashboard() {
350350

351351

352352

353+
if (error) return <div className="p-6 text-red-400">{error}</div>
354+
353355
if (!summary) {
354356
return (
355357
<div className="h-full w-full flex flex-col p-4 space-y-4">
@@ -361,8 +363,6 @@ export default function Dashboard() {
361363
)
362364
}
363365

364-
if (error) return <div className="p-6 text-red-400">{error}</div>
365-
366366
return (
367367
<div className="h-full w-full flex flex-col rounded-r-xl overflow-hidden">
368368
<MriPageHeader title={activeView === 'dashboard' ? t('qadmin.page.dashboard') : t('qadmin.page.commands')} icon={activeView === 'dashboard' ? LayoutDashboard : Terminal}>

web/src/pages/LiveScreensPage.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ export default function LiveScreensPage() {
2828
// Fetch online-only player list
2929
useEffect(() => {
3030
sendNui('getPlayers', { page: 1, limit: 200, search: '' }, { data: MOCK_PLAYERS }).then((res: any) => {
31-
const list = res.data || (Array.isArray(res) ? res : [])
32-
if (list) setPlayers(list.filter((p: any) => p.online === true))
33-
})
31+
const list = res?.data || (Array.isArray(res) ? res : [])
32+
if (Array.isArray(list)) setPlayers(list.filter((p: any) => p.online === true))
33+
}).catch((e) => console.error('getPlayers failed', e))
3434
}, [sendNui])
3535

3636
// Use a ref to track active streams for unmount cleanup

web/src/pages/Logs.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,10 @@ export default function Logs() {
933933
const [settingsDraft, setSettingsDraft] = useState<LogSettingsData | null>(null)
934934
const [settingsSaving, setSettingsSaving] = useState(false)
935935

936+
// Client-generated stable ids for live logs that arrive without a server id.
937+
// Negative and decrementing so they never collide with server-assigned (positive) ids.
938+
const liveLogIdSeq = useRef(0)
939+
936940
const pendingSearch = useRef<ReturnType<typeof setTimeout> | null>(null)
937941
const [debouncedSearch, setDebouncedSearch] = useState('')
938942
const [debouncedResource, setDebouncedResource] = useState('')
@@ -1018,7 +1022,8 @@ export default function Logs() {
10181022
const matchesSearch = !debouncedSearch || log.message.toLowerCase().includes(debouncedSearch.toLowerCase()) || log.admin.toLowerCase().includes(debouncedSearch.toLowerCase())
10191023
if (matchesCategory && matchesLevel && matchesResource && matchesSearch) {
10201024
if (page === 1) {
1021-
setLogs(prev => [log, ...prev.slice(0, PAGE_SIZE - 1)])
1025+
const stableLog: LogEntry = log.id != null ? log : { ...log, id: --liveLogIdSeq.current }
1026+
setLogs(prev => [stableLog, ...prev.slice(0, PAGE_SIZE - 1)])
10221027
setTotal(prev => prev + 1)
10231028
} else {
10241029
setNewCount(prev => prev + 1)

web/src/pages/Permissions/components/GroupEditor.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,19 @@ export default function GroupEditor({ group, onBack }: { group: GroupData, onBac
126126

127127
const handleSave = async () => {
128128
setSaving(true)
129-
const response: any = await sendNui('mri_Qadmin:server:UpdateGroupPermissions', {
130-
id: group.id,
131-
permissions: Array.from(permissions),
132-
linkedPrincipals,
133-
})
134-
setSaving(false)
135-
if (response?.status === 'ok') {
136-
onBack()
129+
try {
130+
const response: any = await sendNui('mri_Qadmin:server:UpdateGroupPermissions', {
131+
id: group.id,
132+
permissions: Array.from(permissions),
133+
linkedPrincipals,
134+
})
135+
if (response?.status === 'ok') {
136+
onBack()
137+
}
138+
} catch (e) {
139+
console.error('UpdateGroupPermissions failed', e)
140+
} finally {
141+
setSaving(false)
137142
}
138143
}
139144

web/src/pages/Permissions/components/PlayerGroups.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export default function PlayerGroups({ groups, searchQuery }: { groups: GroupDat
5959

6060

6161
const loadPlayerGroups = async (player: Player) => {
62+
if (!player.citizenid) return
6263
setLoading(true)
6364
try {
6465
const cid = player.citizenid
@@ -90,11 +91,16 @@ export default function PlayerGroups({ groups, searchQuery }: { groups: GroupDat
9091
const handleSave = async () => {
9192
if (!activePlayer?.citizenid) return
9293
setLoading(true)
93-
const response: any = await sendNui('mri_Qadmin:server:UpdateCharacterGroups', { citizenid: activePlayer.citizenid, groups: Array.from(selectedGroups) })
94-
if (response?.status === 'ok') {
95-
await loadPlayerGroups(activePlayer)
94+
try {
95+
const response: any = await sendNui('mri_Qadmin:server:UpdateCharacterGroups', { citizenid: activePlayer.citizenid, groups: Array.from(selectedGroups) })
96+
if (response?.status === 'ok') {
97+
await loadPlayerGroups(activePlayer)
98+
}
99+
} catch (e) {
100+
console.error('UpdateCharacterGroups failed', e)
101+
} finally {
102+
setLoading(false)
96103
}
97-
setLoading(false)
98104
}
99105

100106
return (
@@ -109,9 +115,9 @@ export default function PlayerGroups({ groups, searchQuery }: { groups: GroupDat
109115
</div>
110116
) : (
111117
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
112-
{playersList.map((p) => (
113-
<div
114-
key={p.citizenid || p.id}
118+
{playersList.map((p, i) => (
119+
<div
120+
key={p.citizenid || p.id || `player-${i}`}
115121
onClick={() => loadPlayerGroups(p)}
116122
className="bg-card border border-border/50 rounded-xl p-4 flex items-center gap-4 cursor-pointer hover:border-primary/50 hover:bg-primary/5 transition-all group shadow-sm hover:shadow-md"
117123
>

web/src/pages/Players.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -999,9 +999,13 @@ export default function Players() {
999999
variant="ghost" size="icon"
10001000
className="h-7 w-7 text-red-500 hover:text-red-400 hover:bg-red-500/10 rounded-none"
10011001
onClick={async () => {
1002-
await sendNui('removeVipPlayer', { citizenid: selectedPlayer.citizenid })
1003-
setVipRankMap(prev => { const n = { ...prev }; delete n[selectedPlayer.citizenid || '']; return n })
1004-
setVipCitizenIds(prev => { const n = new Set(prev); n.delete(selectedPlayer.citizenid || ''); return n })
1002+
try {
1003+
await sendNui('removeVipPlayer', { citizenid: selectedPlayer.citizenid })
1004+
setVipRankMap(prev => { const n = { ...prev }; delete n[selectedPlayer.citizenid || '']; return n })
1005+
setVipCitizenIds(prev => { const n = new Set(prev); n.delete(selectedPlayer.citizenid || ''); return n })
1006+
} catch (e) {
1007+
console.error('removeVipPlayer failed', e)
1008+
}
10051009
}}
10061010
>
10071011
<UserMinus className="w-3.5 h-3.5" />
@@ -1246,7 +1250,7 @@ export default function Players() {
12461250
vehicles: selectedPlayer.vehicles.filter((v: any) => v.plate !== pendingDeletePlate)
12471251
}
12481252
setSelectedPlayer(updated)
1249-
setPlayers(prev => prev.map(p => p.id === updated.id ? updated : p))
1253+
setPlayers(prev => prev.map(p => getPlayerKey(p) === getPlayerKey(updated) ? updated : p))
12501254
}
12511255

12521256
await sendAction({ event: 'mri_Qadmin:server:DeleteVehicleByPlate', type: 'server', perms: 'qadmin.action.delete_vehicle' }, { Plate: { value: pendingDeletePlate } })

0 commit comments

Comments
 (0)