vipexv / v-staffchat-2.0

Advanced Standalone NUI Based Staff Chat for FiveM.
Other
13 stars 7 forks source link

Just a request of help.. #3

Closed CaptainExorY closed 8 months ago

CaptainExorY commented 9 months ago

Im on my jurney of programming and currently working on your script. I wanted to add a User view and would love to get some guidiance from you. I already added a new list into the /server/core.lua where normal users that joins gets into.

Basically copied your code from /web/src/components/OnlineStaffTab.tsx to my file and changed the values

in the app.tsx: const App: React.FC = () => { const [visible, setVisible] = useState(false); const [sourceData, setSourceData] = useState({ id: 0, name: "", isStaff: false, }); const [messages, setMessages] = useState<Message[]>([]); const [activeStaff, setActiveStaff] = useState<StaffMember[]>([]); const [activeUsers, setActiveUsers] = useState<OnlineUsers[]>([]); ////////MY LINE

and obviously changed more, copied your adminData in the shared/types.lua and named it playerData.. but im stuck here now...

CaptainExorY commented 9 months ago
// React
import React, { useState, useEffect } from "react";

// CSS
import "./App.css";

// Cool :o
import { debugData } from "./utils/debugData";
import { fetchNui } from "./utils/fetchNui";
import { useNuiEvent } from "./hooks/useNuiEvent";
import { isEnvBrowser } from "./utils/misc";
import { motion } from "framer-motion";

// Mantine
import { Tabs, Transition } from "@mantine/core";
import { notifications } from "@mantine/notifications";
import { Cog, Info, MessageSquare, Send, Users } from "lucide-react";
import ChatTab from "./components/ChatTab";
import OnlineStaffTab from "./components/OnlineStaffTab";
import OnlineUsersTab from "./components/OnlineTestTab";
import SettingsTab from "./components/SettingsTab";

debugData([
  {
    action: "setVisible",
    data: true,
  },
]);

// debugData([
//   {
//     action: "staffchat:nui:firemessage",
//     data: {
//       adminData: {
//         id: 1,
//         name: "vipex",
//         isStaff: true,
//       },
//       date_time: "N/A",
//       inputData:
//         "/image https://cdn.discordapp.com/attachments/839129248265666589/1178613078653415475/image2_1.jpg?ex=65800277&is=656d8d77&hm=3df933ad893f02e8e6ce3d5b610ba23ea478809799cb951158038bd58f8b0549&",
//     },
//   },
// ]);

interface Message {
  adminData: StaffMember;
  inputData: string;
  date_time: string;
}

interface StaffMember {
  id: string | number;
  name: string;
  // Unused but it's in there.
  isStaff: boolean;
}

interface OnlineUsers {
  id: string | number;
  name: string;
  // Unused but it's in there.
  isStaff: boolean;
}

interface Settings {
  theme: string;
  notifications: boolean;
}

const initialSettings: Settings = {
  theme: "default",
  notifications: true,
};

const App: React.FC = () => {
  const [visible, setVisible] = useState(false);
  const [sourceData, setSourceData] = useState<StaffMember>({
    id: 0,
    name: "",
    isStaff: false,
  });
  const [messages, setMessages] = useState<Message[]>([]);
  const [activeStaff, setActiveStaff] = useState<StaffMember[]>([]);
  const [activeUsers, setActiveUsers] = useState<OnlineUsers[]>([]);

  const [settings, setSettings] = useState<Settings>(initialSettings);

  debugData([
    {
      action: "staffchat:nui:admins",
      data: [
        { isStaff: true, name: "vipex", id: "1" },
        { isStaff: true, name: "vipex_2", id: "2" },
        { isStaff: true, name: "vipex", id: "3" },
        { isStaff: true, name: "vipex_2", id: "4" },
        { isStaff: true, name: "vipex", id: "5" },
        { isStaff: true, name: "vipex_2", id: "6" },
        { isStaff: true, name: "vipex", id: "7" },
        { isStaff: true, name: "vipex_2", id: "8" },
      ],
    },
  ]);

  useNuiEvent("staffchat:clear", () => {
    setMessages([]);
  });

  useNuiEvent<Settings>("staffchat:nui:settings", (psettings) => {
    setSettings(psettings);
  });

  useNuiEvent("staffchat:nui:notify", (info) => {
    if (!info) return;
    notifications.show({
      title: "Staffchat",
      message: info,
      icon: <Info size={"16px"} />,
    });
  });

  useNuiEvent("staffchat:nui:sourcedata", setSourceData);

  useNuiEvent<Message>("staffchat:nui:firemessage", (data) => {
    if (!data) return;

    setMessages([...messages, data]);

    if (!visible && settings.notifications) {
      notifications.show({
        title: "StaffChat | Neue Nachricht!",
        message: `${data.adminData.name} [ID - ${data.adminData.id}] hat eine Nachricht gesendet!`,
        icon: <Send size={16} />,
      });
    }
  });

  // useNuiEvent("staffchat:nui:admins", (staff: StaffMember[]) => {
  //   console.log(JSON.stringify(staff));
  // });

  useNuiEvent("staffchat:nui:admins", setActiveStaff);

  useNuiEvent("staffchat:nui:users", setActiveUsers);

  useNuiEvent<boolean>("setVisible", setVisible);

  useEffect(() => {
    if (!visible) return;

    const keyHandler = (e: KeyboardEvent) => {
      if (["Escape"].includes(e.code)) {
        if (!isEnvBrowser()) fetchNui("hideFrame");
        else setVisible(!visible);
      }
    };

    window.addEventListener("keydown", keyHandler);

    return () => window.removeEventListener("keydown", keyHandler);
  }, [visible]);

  return (
    <>
      <Transition
        mounted={visible}
        duration={400}
        transition={"slide-up"}
        timingFunction="ease"
      >
        {(styles) => (
          <div style={styles} className="absolute bottom-2 right-10">
            <Tabs
              color="dark"
              variant="pills"
              orientation="vertical"
              styles={{
                tab: {
                  transition: "0.4s",
                },
              }}
              className={` ${
                settings.theme === "default" ? "bg-[#2e2e2e]" : "bg-[#1a1a1a]"
              } transition text-white rounded p-2 border border-[#1a1a1a] flex flex-row gap-10`}
              defaultValue="chat"
            >
              <Tabs.List className="flex flex-col p-2 gap-2 font-bold">
                <Tabs.Tab
                  value="chat"
                  leftSection={<MessageSquare className="bg-blue" size={16} />}
                  className="p-1"
                >
                  Chat
                </Tabs.Tab>
                <Tabs.Tab value="onlineStaff" leftSection={<Users size={16} />}>
                  Teamler
                </Tabs.Tab>
                <Tabs.Tab value="onlineUsers" leftSection={<Users size={16} />}>
                  User
                </Tabs.Tab>
                <Tabs.Tab value="settings" leftSection={<Cog size={16} />}>
                  Einstellungen
                </Tabs.Tab>
              </Tabs.List>

              <Tabs.Panel value="chat">
                <ChatTab
                  messages={messages}
                  sourceData={sourceData}
                  userSettings={settings}
                />
              </Tabs.Panel>

              <Tabs.Panel value="onlineStaff">
                <OnlineStaffTab
                  staffMembers={activeStaff}
                  userSettings={settings}
                />
              </Tabs.Panel>

              <Tabs.Panel value="onlineUsers">
                <OnlineUsersTab
                  userMembers={activeUsers}
                  userSettings={settings}
                />
              </Tabs.Panel>

              <Tabs.Panel value="settings">
                <SettingsTab userSettings={settings} />
              </Tabs.Panel>
            </Tabs>
          </div>
        )}
      </Transition>
    </>
  );
};

export default App;
import { User } from "lucide-react";
import "../App.css";
import { ScrollArea } from "@mantine/core";

interface Props {
  userMembers: UserMember[];
  userSettings: any;
}

interface Message {
  playerData: UserMember;
  inputData: string;
  date_time: string;
}

interface UserMember {
  id: string | number;
  name: string;
  isStaff: boolean;
}

const OnlineTestTab: React.FC<Props> = ({ userMembers, userSettings }) => {
  return (
    <>
      <div
        className={`w-[46dvh] h-[46dvh] ${
          userSettings.theme === "default" ? "bg-[#1a1a1a]" : "bg-[#2a2a2a]"
        } rounded bg-opacity-50 transition`}
      >
        <ScrollArea h={500}>
          <div className=" m-1 grid gap-2 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
            {Object.values(userMembers).map(
              (staff: UserMember, index: number) => {
                if (!staff) return;
                console.log(index);
                return (
                  <div
                    className={`rounded-[2px] text-xs flex justify-between px-2 py-1 ${
                      userSettings.theme === "default"
                        ? "bg-[#1a1a1a]"
                        : "bg-[#2a2a2a]"
                    }`}
                    key={index}
                  >
                    <p className="flex justify-center items-center">
                      {staff.name}
                    </p>

                    <p className="bg-blue-500 rounded-[2px] p-1 text-xs font-main text-opacity-50 font-medium">
                      ID: {staff.id}
                    </p>
                  </div>
                );
              }
            )}
          </div>
        </ScrollArea>
      </div>
    </>
  );
};

export default OnlineTestTab;

The core is now just for testing, not final

--server/core.lua
---@type adminData[]
AdminData = {}

---@type playerData[]
PlayerData = {}

---@param _source string
---@param _oldID string
AddEventHandler("playerJoining", function(_source, _oldID)
  if source <= 0 then
    return Debug("source is nil.")
  end

  local player = CPlayer:new(source)

  if not player then
    return Debug("CPlayer:new method is returning null.")
  end

  if player.isStaff then
    AdminData[tostring(source)] = player
    Debug(player.name, "was added to the AdminData table.")
    PlayerData[tostring(source)] = player
    Debug(player.name, "was added to the PlayerData table.")
  else 
    PlayerData[tostring(source)] = player
    Debug(player.name, "was added to the PlayerData table.")
  end
end)

AddEventHandler("playerDropped", function(_reason)
  if AdminData[tostring(source)] then
    AdminData[tostring(source)] = nil
    Debug("[netEvent:playerDropped] Event was triggered, and the player was removed from the AdminData table.")
  else
  PlayerData[tostring(source)] = nil
  Debug("[netEvent:playerDropped] Event was triggered, and the player was removed from the PlayerData table.")
  end
end)

SetTimeout(200, function()
  local OnlinePlayers = GetPlayers()

  Debug("AdminData table before looping through all players: ", json.encode(AdminData))
  Debug("PlayerData table before looping through all players: ", json.encode(PlayerData))

  for i = 1, #OnlinePlayers do
    local playerSource = OnlinePlayers[i]
    local player = CPlayer:new(playerSource)

    if not player then
      return Debug("[timeout:function] CPlayer:new is returning nil.")
    end

    if player.isStaff then
      AdminData[tostring(playerSource)] = player
      Debug(player.name, "was added to the AdminData table.")
      PlayerData[tostring(playerSource)] = player
      Debug(player.name, "was added to the PlayerData table.")
    else 
      Debug("Nothing right now.")
    end
  end

  Debug("AdminData table after looping through all of the players: ", json.encode(AdminData))
  Debug("PlayerData table after looping through all of the players: ", json.encode(PlayerData))
end)
--server/events.lua
RegisterNetEvent("staffchat:server:admins", function()
  if not source then
    return Debug("[staffchat:server:admins] Event was called but source is nil.")
  end

  if not AdminData[tostring(source)] then
    -- TODO: Notification system.
    return Debug("[netEvent:staffchat:server:admins] Player is not a staff member.")
  end

  TriggerClientEvent("staffchat:client:admins", source, AdminData)
end)

RegisterNetEvent("staffchat:server:users", function()
  if not source then
    return Debug("[staffchat:server:users] Event was called but source is nil.")
  end

  if PlayerData[tostring(source)] then
    -- TODO: Notification system.
    return Debug("[netEvent:staffchat:server:users] Player is member.")
  end

  TriggerClientEvent("staffchat:client:users", source, PlayerData)
end)

---@param data messageInfo
RegisterNetEvent("staffchat:server:firemessage", function(data)
  if not source or not AdminData[tostring(source)] then
    return Debug("source is nil or the player isn't a staff member.")
  end

  if not next(data) then
    return Debug("[netEvent:staffchat:server:firemessage] Event was called, but the first param is null/missing.")
  end

  data.adminData = AdminData[tostring(source)]

  Debug("[netEvent:staffchat:server:firemessage] Data: ", json.encode(data))

  for _, v in pairs(AdminData) do
    ---@diagnostic disable-next-line: param-type-mismatch
    TriggerClientEvent("staffchat:client:firemessage", v.id, data)
  end
end)

RegisterNetEvent("staffchat:server:permissions", function()
  if not AdminData[tostring(source)] then
    Debug("[netEvent:staffchat:server:permissions] Player is not staff.")

    -- Not the best, but it works.

    local exData = {
      id = source,
      name = GetPlayerName(source),
      isStaff = false
    }

    TriggerClientEvent("staffchat:client:permissions", source, exData)
    return
  end

  Debug("[netEvent:staffchat:server:permissions] AdminData[tostring(source)]:", json.encode(AdminData[tostring(source)]))
  TriggerClientEvent("staffchat:client:permissions", source, AdminData[tostring(source)])
end)
-- client/events.lua
RegisterNetEvent("UIMessage", function(action, data)
  UIMessage(action, data)
end)

RegisterNetEvent("staffchat:client:admins", function(data)
  if not data or not next(data) then
    return Debug("[staffchat:client:admins] Event was called but the first param null or not a table, param: ",
      json.encode(data))
  end

  Debug("[staffchat:client:admins] Data param:", json.encode(data))

  UIMessage("staffchat:nui:admins", data)
end)

RegisterNetEvent("staffchat:client:users", function(data)
  if not data or not next(data) then
    return Debug("[staffchat:client:users] Event was called but the first param null or not a table, param: ",
      json.encode(data))
  end

  Debug("[staffchat:client:users] Data param:", json.encode(data))

  UIMessage("staffchat:nui:users", data)
end)

RegisterNetEvent("staffchat:client:firemessage", function(data)
  if not data or not next(data) then
    return Debug("[staffchat:client:firemessage] Event was called but data parm is null or not a table, param: ",
      json.encode(data))
  end

  Debug("[staffchat:client:firemessage] data param: ", json.encode(data))
  UIMessage("staffchat:nui:firemessage", data)
end)

RegisterNetEvent("staffchat:client:permissions", function(data)
  if not data or not next(data) then
    return Debug("[staffchat:client:permissions] Event triggered, but the first param is null or not a table,param: ",
      json.encode(data))
  end

  UIMessage("staffchat:nui:sourcedata", data)

  PlayerData = data
end)
RegisterKeyMapping("staffchat", "Staff Chat", "keyboard", "F4")
vipexv commented 8 months ago

I'm so sorry, I never got to this, I'm not on my Pc right now but if you need help then just send me a dm on discord/ @vipex.v