Skip to content

Commit

Permalink
wip(web/actionModal): ids tab and partial info tab
Browse files Browse the repository at this point in the history
  • Loading branch information
tabarra committed Mar 31, 2024
1 parent de5bd17 commit 0f4d66b
Show file tree
Hide file tree
Showing 9 changed files with 370 additions and 35 deletions.
73 changes: 73 additions & 0 deletions panel/src/layout/ActionModal/ActionIdsTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { txToast } from "@/components/TxToaster";
import { cn, copyToClipboard } from "@/lib/utils";
import { CopyIcon } from "lucide-react";
import { useState } from "react";
import { DatabaseActionType } from "../../../../core/components/PlayerDatabase/databaseTypes";


type IdsBlockProps = {
title: string,
emptyMessage: string,
ids: string[] | undefined,
isSmaller?: boolean,
}
function IdsBlock({ title, emptyMessage, ids, isSmaller }: IdsBlockProps) {
const [hasCopiedIds, setHasCopiedIds] = useState(false);

const handleCopyIds = () => {
if(!ids) return;
copyToClipboard(ids.join('\n')).then((res) => {
if (res !== false) {
setHasCopiedIds(true);
} else {
txToast.error('Failed to copy to clipboard :(');
}
}).catch((error) => {
txToast.error({
title: 'Failed to copy to clipboard:',
msg: error.message,
});
});
}

return <div>
<div className="flex justify-between items-center pb-1">
<h3 className="text-xl">{title}</h3>
{/* {hasCopiedIds ? (
<span className="text-sm text-success-inline">Copied!</span>
) : (
// TODO: a button to erase the ids from the database can be added here,
// requires tooltip and confirm modal though
<button onClick={handleCopyIds}>
<CopyIcon className="h-4 text-secondary hover:text-primary" />
</button>
)} */}
</div>
<p className={cn(
"font-mono break-all whitespace-pre-wrap border rounded divide-y divide-border/50 text-muted-foreground",
isSmaller ? "text-2xs leading-5 font-extralight tracking-widest" : "text-xs leading-6 tracking-wider"
)}>
{!Array.isArray(ids) || ids.length === 0 && <span className="block px-1 opacity-50 italic">{emptyMessage}</span>}
{ids!.map((id) => (
<span key={id} className="block px-1 font-semibold">{id}</span>
))}
</p>
</div>
}


export default function ActionIdsTab({ action }: { action: DatabaseActionType }) {
return <div className="flex flex-col gap-4 p-1">
<IdsBlock
title="Target Identifiers"
emptyMessage="This action targets no identifiers."
ids={action.ids}
/>
<IdsBlock
title="Target Hardware IDs"
emptyMessage="This action targets no hardware IDs."
ids={action.hwids}
isSmaller
/>
</div>;
}
90 changes: 90 additions & 0 deletions panel/src/layout/ActionModal/ActionInfoTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { tsToLocaleDateTime } from "@/lib/utils";
import { useRef, useState } from "react";
import { DatabaseActionType } from "../../../../core/components/PlayerDatabase/databaseTypes";
import { useOpenPlayerModal } from "@/hooks/playerModal";



const calcTextAreaLines = (text?: string) => {
if (!text) return 3;
const lines = text.trim().split('\n').length + 1;
return Math.min(Math.max(lines, 3), 16);
}

function ActionReasonBox({ actionReason }: { actionReason: string }) {
const textAreaRef = useRef<HTMLTextAreaElement>(null);
const [textAreaLines, setTextAreaLines] = useState(calcTextAreaLines(actionReason));

return <>
<Label htmlFor="playerNotes">
Reason:
</Label>
<Textarea
ref={textAreaRef}
id="playerNotes"
className="w-full mt-1"
readOnly={true}
value={actionReason}
//1rem of padding + 1.25rem per line
style={{ height: `${1 + 1.25 * textAreaLines}rem` }}
/>
</>
}


type ActionInfoTabProps = {
actionId: string;
action: DatabaseActionType;
setSelectedTab: (t: string) => void;
refreshModalData: () => void;
}

export default function ActionInfoTab({ actionId, action, setSelectedTab, refreshModalData }: ActionInfoTabProps) {
const actionDateTimeText = tsToLocaleDateTime(action.timestamp);
const openPlayerModal = useOpenPlayerModal();

const targetLicenses = action.ids.filter(id => id.startsWith('license:'));
const linkedPlayer = targetLicenses.length === 1 ? targetLicenses[0].split(':')[1] : false;
const handleViewPlayerClick = () => {
if (!linkedPlayer) return;
openPlayerModal({ license: linkedPlayer });
}

return <div className="p-1">
<dl className="pb-2">
<div className="py-0.5 grid grid-cols-3 gap-4 px-0">
<dt className="text-sm font-medium leading-6 text-muted-foreground">Date/Time</dt>
<dd className="text-sm leading-6 col-span-2 mt-0">{actionDateTimeText}</dd>
</div>
<div className="py-0.5 grid grid-cols-3 gap-4 px-0">
<dt className="text-sm font-medium leading-6 text-muted-foreground">Admin</dt>
<dd className="text-sm leading-6 col-span-2 mt-0">{action.author}</dd>
</div>
<div className="py-0.5 grid grid-cols-3 gap-4 px-0">
<dt className="text-sm font-medium leading-6 text-muted-foreground">Player</dt>
<dd className="text-sm leading-6 col-span-2x mt-0">{action.playerName}</dd>
<dd className="text-right">
<Button
variant="outline"
size='inline'
style={{ minWidth: '8.25ch' }}
onClick={handleViewPlayerClick}
disabled={!linkedPlayer}
>
View
</Button>
</dd>
</div>
<div className="py-0.5 grid grid-cols-3 gap-4 px-0">
<dt className="text-sm font-medium leading-6 text-muted-foreground">Status</dt>
<dd className="text-sm leading-6 col-span-2 mt-0">FIXME:</dd>
</div>

</dl>

<ActionReasonBox actionReason={action.reason} />
</div>;
}
42 changes: 19 additions & 23 deletions panel/src/layout/ActionModal/ActionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { useActionModalStateValue } from "@/hooks/actionModal";
import { InfoIcon, ListIcon, HistoryIcon, GavelIcon } from "lucide-react";
import { InfoIcon, ListIcon, HistoryIcon, GavelIcon, PenLineIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { ScrollArea } from "@/components/ui/scroll-area";
import GenericSpinner from "@/components/GenericSpinner";
Expand All @@ -14,26 +14,29 @@ import { useBackendApi } from "@/hooks/fetch";
import ActionModalFooter from "./ActionModalFooter";
import ModalCentralMessage from "@/components/ModalCentralMessage";
import { HistoryActionModalResp, HistoryActionModalSuccess } from "@shared/historyApiTypes";
import ActionIdsTab from "./ActionIdsTab";
import ActionInfoTab from "./ActionInfoTab";


const modalTabs = [
{
title: 'Info',
icon: <InfoIcon className="mr-2 h-5 w-5 hidden xs:block" />,
},
{
title: 'History',
icon: <HistoryIcon className="mr-2 h-5 w-5 hidden xs:block" />,
},
//Maybe in the future for logs of changes?
// {
// title: 'History',
// icon: <HistoryIcon className="mr-2 h-5 w-5 hidden xs:block" />,
// },
{
title: 'IDs',
icon: <ListIcon className="mr-2 h-5 w-5 hidden xs:block" />,
},
{
title: 'Ban',
icon: <GavelIcon className="mr-2 h-5 w-5 hidden xs:block" />,
className: 'hover:bg-destructive hover:text-destructive-foreground',
}
// {
// title: 'Edit',
// icon: <PenLineIcon className="mr-2 h-5 w-5 hidden xs:block" />,
// // className: 'hover:bg-destructive hover:text-destructive-foreground',
// }
]


Expand Down Expand Up @@ -131,6 +134,7 @@ export default function ActionModal() {
'w-full tracking-wider justify-center md:justify-start',
'h-7 rounded-sm px-2 text-sm',
'md:h-10 md:text-base',
// @ts-ignore annoying, remove this when adding some class to any of the tabs
tab.className,
)}
onClick={() => setSelectedTab(tab.title)}
Expand All @@ -151,23 +155,15 @@ export default function ActionModal() {
</ModalCentralMessage>
) : (
<>
{/* {selectedTab === 'Info' && <InfoTab
actionRef={actionRef!}
player={modalData.player}
{selectedTab === 'Info' && <ActionInfoTab
actionId={actionRef!}
action={modalData.action}
setSelectedTab={setSelectedTab}
refreshModalData={refreshModalData}
/>}
{selectedTab === 'History' && <HistoryTab
actionHistory={modalData.player.actionHistory}
serverTime={modalData.serverTime}
refreshModalData={refreshModalData}
/>}
{selectedTab === 'IDs' && <IdsTab
player={modalData.player}
{selectedTab === 'IDs' && <ActionIdsTab
action={modalData.action}
/>}
{selectedTab === 'Ban' && <BanTab
actionRef={actionRef!}
/>} */}
</>
)}
</ScrollArea>
Expand Down
Loading

0 comments on commit 0f4d66b

Please sign in to comment.