Skip to content

Commit

Permalink
fix: profile fetching optimized
Browse files Browse the repository at this point in the history
  • Loading branch information
BubbleDK committed Aug 30, 2024
1 parent d0ccd1d commit a2de949
Show file tree
Hide file tree
Showing 18 changed files with 987 additions and 858 deletions.
1 change: 1 addition & 0 deletions client/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ serverNuiCallback('getWarrants')
-- Profiles
serverNuiCallback('getAllProfiles')
serverNuiCallback('getProfile')
serverNuiCallback('getProfiles')
serverNuiCallback('saveProfileNotes')
serverNuiCallback('isProfileWanted')
serverNuiCallback('updateProfileImage')
Expand Down
2 changes: 1 addition & 1 deletion fxmanifest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ game 'gta5'

--[[ Resource Information ]]--
name 'bub-mdt'
version '0.0.21'
version '0.0.22'
license 'GPL-3.0-or-later'
author 'Bubble'

Expand Down
6 changes: 6 additions & 0 deletions server/db.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local db = {}
local config = require 'config'
local framework = require(('server.framework.%s'):format(config.framework))
local profileCards = require 'server.profileCards'
local utils = require 'server.utils'

function createRecentActivity(citizenid, category, type, activityid)
local insertRecentActivityQuery = [[
Expand Down Expand Up @@ -70,6 +71,11 @@ function db.selectAllProfiles()
return framework.getAllProfiles()
end

function db.selectProfiles(page, search)
local offset = (page - 1) * 10
return utils.dbSearch(framework.getProfiles, search, offset)
end

function db.selectCharacterProfile(citizenid)
local parameters = { citizenid }
local profile = framework.getCharacterProfile(parameters)
Expand Down
44 changes: 44 additions & 0 deletions server/framework/qbx_core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ local selectProfiles = [[
mdt_profiles profile
ON
profile.citizenid = players.citizenid
LIMIT 10 OFFSET ?
]]

function qbx.getAllProfiles()
Expand All @@ -192,6 +193,49 @@ function qbx.getAllProfiles()
return profiles
end

local selectProfilesFilter = selectProfiles:gsub('LIMIT', [[
WHERE
players.citizenid LIKE ?
OR CONCAT(
JSON_UNQUOTE(JSON_EXTRACT(players.charinfo, '$.firstname')),
' ',
JSON_UNQUOTE(JSON_EXTRACT(players.charinfo, '$.lastname'))
) LIKE ?
GROUP BY
players.citizenid
LIMIT
]])

function qbx.getProfiles(parameters, filter)
local query, params

if filter then
local searchInput = parameters[1]
params = { "%" .. searchInput .. "%", "%" .. searchInput .. "%", parameters[2] }

query = selectProfilesFilter
else
query = selectProfiles
params = parameters
end

local profilesResult = MySQL.rawExecute.await(query, params)
local profiles = {}

for _, v in pairs(profilesResult) do
local charinfo = json.decode(v.charinfo)
profiles[#profiles+1] = {
citizenid = v.citizenid,
firstname = charinfo.firstname,
lastname = charinfo.lastname,
dob = charinfo.birthdate,
image = v.image,
}
end

return profiles
end

function qbx.getDriverPoints(citizenid)
local result = MySQL.rawExecute.await('SELECT SUM(COALESCE(points, 0) * COALESCE(count, 1)) AS total_points FROM mdt_incidents_charges WHERE citizenid = ?', { citizenid })?[1]
if (result.total_points) then return result.total_points end
Expand Down
9 changes: 9 additions & 0 deletions server/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ utils.registerCallback('mdt:getAllProfiles', function(source, data)
}
end)

utils.registerCallback('mdt:getProfiles', function(source, data)
local profiles = db.selectProfiles(data.page, data.search)

return {
hasMore = #profiles == 10 or false,
profiles = profiles
}
end)

utils.registerCallback('mdt:getProfile', function(source, data)
return db.selectCharacterProfile(data)
end)
Expand Down
8 changes: 8 additions & 0 deletions server/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,12 @@ function utils.cleanTable(tab)
return newTable
end

function utils.dbSearch(fn, search, offset)
if not search or search == '' then
return fn({ offset })
end

return fn({ search, offset }, true)
end

return utils
706 changes: 0 additions & 706 deletions web/build/assets/index-0518d1ff.js

This file was deleted.

706 changes: 706 additions & 0 deletions web/build/assets/index-50c93c57.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion web/build/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css"
/>
<title>Bubble MDT</title>
<script type="module" crossorigin src="./assets/index-0518d1ff.js"></script>
<script type="module" crossorigin src="./assets/index-50c93c57.js"></script>
<link rel="stylesheet" href="./assets/index-eeb3b938.css">
</head>
<body>
Expand Down
15 changes: 15 additions & 0 deletions web/src/helpers/removePages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { queryClient } from "../main";

export function removePages(queryKey: string[]) {
queryClient.setQueriesData<{ pages: unknown[][]; pageParams: number[] }>(
{ queryKey: queryKey },
(data) => {
if (!data) return;

return {
pages: data.pages.slice(0, 1),
pageParams: data.pageParams.slice(0, 1),
};
}
);
}
21 changes: 21 additions & 0 deletions web/src/hooks/useInfiniteScroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from "react";
import { useIntersection } from "@mantine/hooks";

export const useInfiniteScroll = (
onIntersect: () => void,
threshold?: number
) => {
const lastElementRef = React.useRef(null);
const { ref, entry } = useIntersection({
root: lastElementRef.current,
threshold: threshold || 1.0,
});

React.useEffect(() => {
if (entry && entry.isIntersecting) {
onIntersect();
}
}, [entry]);

return { ref };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from "react";
import { Text, Image } from "@mantine/core";
import { useProfilesStore } from "../../../../../stores";
import { DEBUG_PROFILE } from "../../../../../stores/profilesStore";
import { PartialProfileData, Profile } from "../../../../../typings";
import { fetchNui } from "../../../../../utils/fetchNui";
import locales from "../../../../../locales";
import dayjs from "dayjs";

interface Props {
profile: PartialProfileData;
}

const PartialProfile: React.ForwardRefRenderFunction<
HTMLDivElement | null,
Props
> = ({ profile }, ref) => {
const { setIsProfileWanted, setSelectedProfile } = useProfilesStore();

const handleProfileClick = async (profile: PartialProfileData) => {
setSelectedProfile(null);
setIsProfileWanted(false);
const resp = await fetchNui<Profile>("getProfile", profile.citizenid, {
data: {
...DEBUG_PROFILE,
firstName: profile.firstname,
lastName: profile.lastname,
citizenid: profile.citizenid,
},
});

const isProfileWanted = await fetchNui<boolean>(
"isProfileWanted",
profile.citizenid,
{
data: false,
}
);

setSelectedProfile(resp);
setIsProfileWanted(isProfileWanted);
};

return (
<div
className='profile-card'
onClick={() => handleProfileClick(profile)}
ref={ref}
>
<Image
width={65}
height={65}
src={
profile.image ??
"https://cdn.vectorstock.com/i/preview-1x/97/68/account-avatar-dark-mode-glyph-ui-icon-vector-44429768.jpg"
}
radius={"lg"}
alt='With default placeholder'
withPlaceholder
/>

<div>
<Text weight={600} style={{ fontSize: 13, color: "white" }}>
{profile.firstname} {profile.lastname}
</Text>

<Text style={{ fontSize: 12, color: "white" }}>
{locales.dob}: {dayjs(profile.dob).format("DD/MM/YYYY")}
</Text>

<Text style={{ fontSize: 12, color: "white" }}>
{locales.citizen_id}: {profile.citizenid}
</Text>
</div>
</div>
);
};

export default React.memo(React.forwardRef(PartialProfile));
123 changes: 28 additions & 95 deletions web/src/layers/mdt/components/Profiles/components/ProfileList.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,22 @@
import {
rem,
Input,
Divider,
Text,
Image,
ScrollArea,
Center,
Loader,
} from "@mantine/core";
import { rem, Input, Divider, Text, ScrollArea, Loader } from "@mantine/core";
import { IconUsers, IconSearch } from "@tabler/icons-react";
import "../index.css";
import { useState, useEffect } from "react";
import dayjs from "dayjs";
import { PartialProfileData } from "../../../../../typings";
import { useProfilesStore } from "../../../../../stores";
import { useState, useMemo } from "react";
import locales from "../../../../../locales";
import { useInfiniteScroll } from "../../../../../hooks/useInfiniteScroll";
import PartialProfile from "./PartialProfile";
import { useProfilesQuery } from "../../../../../stores/profilesStore";

interface ProfileListProps {
handleProfileClick: (profile: PartialProfileData) => void;
}

const ProfileList = (props: ProfileListProps) => {
const ProfileList = () => {
const [searchQuery, setSearchQuery] = useState("");
const getProfiles = useProfilesStore((state) => state.getPlayers);
const [profiles, setProfiles] = useState<PartialProfileData[]>([]);
const [filteredProfiles, setFilteredProfiles] =
useState<PartialProfileData[]>(profiles);
const [isLoading, setIsLoading] = useState(false);

useEffect(() => {
setIsLoading(true);
const fetchData = async () => {
return await getProfiles();
};

fetchData().then((data) => {
setProfiles(data.profiles);
setFilteredProfiles(data.profiles);
setIsLoading(false);
});
}, []);
const { data, fetchNextPage, isFetching, isDebouncing } =
useProfilesQuery(searchQuery);
const { ref } = useInfiniteScroll(() => fetchNextPage());

useEffect(() => {
if (searchQuery.trim() === "") {
setFilteredProfiles(profiles);
} else {
const results = profiles.filter(
(profile) =>
(profile.citizenid || "").includes(searchQuery) ||
(profile.firstname || "")
.toLowerCase()
.includes(searchQuery.toLowerCase()) ||
(profile.lastname || "")
.toLowerCase()
.includes(searchQuery.toLowerCase()) ||
(profile.firstname + " " + profile.lastname || "")
.toLowerCase()
.includes(searchQuery.toLowerCase())
);
setFilteredProfiles(results);
}
}, [searchQuery, filteredProfiles]);
const pages = useMemo(() => {
if (!data) return [];
return data.pages.flatMap((page) => page.profiles);
}, [data]);

return (
<div className='content-width'>
Expand Down Expand Up @@ -88,46 +44,23 @@ const ProfileList = (props: ProfileListProps) => {
<div className='profiles-card-content'>
<ScrollArea h={860}>
<div className='profiles-card-content-flex'>
{isLoading ? (
<Center h={"100%"}>
{isDebouncing || isFetching ? (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Loader />
</Center>
) : filteredProfiles.length > 0 ? (
filteredProfiles.map((profile) => (
<div
className='profile-card'
onClick={() => props.handleProfileClick(profile)}
</div>
) : pages.length > 0 ? (
pages.map((profile, i) => (
<PartialProfile
key={profile.citizenid}
>
<Image
width={65}
height={65}
src={
profile.image ??
"https://cdn.vectorstock.com/i/preview-1x/97/68/account-avatar-dark-mode-glyph-ui-icon-vector-44429768.jpg"
}
radius={"lg"}
alt='With default placeholder'
withPlaceholder
/>

<div>
<Text
weight={600}
style={{ fontSize: 13, color: "white" }}
>
{profile.firstname} {profile.lastname}
</Text>

<Text style={{ fontSize: 12, color: "white" }}>
{locales.dob}: {dayjs(profile.dob).format("DD/MM/YYYY")}
</Text>

<Text style={{ fontSize: 12, color: "white" }}>
{locales.citizen_id}: {profile.citizenid}
</Text>
</div>
</div>
profile={profile}
ref={i === pages.length - 2 ? ref : null}
/>
))
) : (
<Text color='dimmed' size='xs'>
Expand Down
Loading

0 comments on commit a2de949

Please sign in to comment.