added user reporting and issue handling

This commit is contained in:
dank074 2021-10-26 00:40:14 -05:00
parent 4603e5271d
commit 710f0f1e01
18 changed files with 350 additions and 170 deletions

View File

@ -2,6 +2,7 @@ import { IFurnitureData, NitroEvent, ObjectDataFactory, PetFigureData, PetRespec
import { GetNitroInstance, GetRoomEngine, GetSessionDataManager, IsOwnerOfFurniture } from '../../../..';
import { InventoryTradeRequestEvent, WiredSelectObjectEvent } from '../../../../../events';
import { FriendsSendFriendRequestEvent } from '../../../../../events/friends/FriendsSendFriendRequestEvent';
import { HelpReportUserEvent } from '../../../../../events/help/HelpReportUserEvent';
import { dispatchUiEvent } from '../../../../../hooks/events';
import { SendMessageHook } from '../../../../../hooks/messages';
import { PetSupplementEnum } from '../../../../../views/room/widgets/avatar-info/common/PetSupplementEnum';
@ -164,6 +165,7 @@ export class RoomWidgetInfostandHandler extends RoomWidgetHandler
case RoomWidgetUserActionMessage.REPORT:
return;
case RoomWidgetUserActionMessage.REPORT_CFH_OTHER:
dispatchUiEvent(new HelpReportUserEvent(userId));
return;
case RoomWidgetUserActionMessage.AMBASSADOR_ALERT_USER:
this.container.roomSession.sendAmbassadorAlertMessage(userId);

View File

@ -5,23 +5,16 @@ export class HelpReportUserEvent extends HelpEvent
public static REPORT_USER: string = 'HCE_HELP_CENTER_REPORT_USER';
private _reportedUserId: number;
private _reportedUsername: string;
constructor(userId: number, username: string)
constructor(userId: number)
{
super(HelpReportUserEvent.REPORT_USER);
this._reportedUserId = userId;
this._reportedUsername = username;
}
public get reportedUserId(): number
{
return this._reportedUserId;
}
public get reportedUsername(): string
{
return this._reportedUsername;
}
}

View File

@ -1,5 +1,5 @@
.nitro-help {
height: 400px;
height: 430px;
width: 300px;
.index-image {

View File

@ -45,14 +45,25 @@ export const SelectReportedChatsView: FC<{}> = props =>
setHelpReportState(reportState);
}, [helpReportState, selectedChats, setHelpReportState]);
const back = useCallback(() =>
{
const reportState = Object.assign({}, helpReportState);
reportState.currentStep = --reportState.currentStep;
setHelpReportState(reportState);
}, [helpReportState, setHelpReportState]);
return (
<>
<div className="d-grid col-12 mx-auto justify-content-center">
<div className="col-12"><h3 className="fw-bold">{LocalizeText('help.emergency.chat_report.subtitle')}</h3></div>
<div className="text-wrap">{LocalizeText('help.emergency.chat_report.description')}</div>
{ userChats.length > 0 &&
<div className="text-wrap">{LocalizeText('help.emergency.chat_report.description')}</div>
}
</div>
{
(userChats.length === 0) && <div>{LocalizeText('help.cfh.error.no_user_data')}</div>
}
{ userChats.length > 0 &&
<>
<NitroCardGridView columns={1}>
@ -65,7 +76,11 @@ export const SelectReportedChatsView: FC<{}> = props =>
)
})}
</NitroCardGridView>
<button className="btn btn-secondary mt-2" type="button" disabled={selectedChats.size <= 0} onClick={submitChats}>{LocalizeText('help.emergency.main.submit.button')}</button>
<div className="d-flex gap-2 justify-content-between mt-auto">
<button className="btn btn-secondary mt-2" type="button" onClick={back}>{LocalizeText('generic.back')}</button>
<button className="btn btn-primary mt-2" type="button" disabled={selectedChats.size <= 0} onClick={submitChats}>{LocalizeText('help.emergency.main.submit.button')}</button>
</div>
</>
}
</>

View File

@ -1,6 +1,6 @@
import { RoomObjectType } from '@nitrots/nitro-renderer';
import { FC, useCallback, useMemo, useState } from 'react';
import { LocalizeText } from '../../../api';
import { GetSessionDataManager, LocalizeText } from '../../../api';
import { NitroCardGridItemView, NitroCardGridView } from '../../../layout';
import { GetChatHistory } from '../../chat-history/common/GetChatHistory';
import { ChatEntryType } from '../../chat-history/context/ChatHistoryContext.types';
@ -19,7 +19,7 @@ export const SelectReportedUserView: FC<{}> = props =>
GetChatHistory().chats
.forEach(chat =>
{
if((chat.type === ChatEntryType.TYPE_CHAT) && (chat.entityType === RoomObjectType.USER))//todo: remove own chats
if((chat.type === ChatEntryType.TYPE_CHAT) && (chat.entityType === RoomObjectType.USER) && (chat.entityId !== GetSessionDataManager().userId))
{
if(!users.has(chat.entityId))
{
@ -47,6 +47,13 @@ export const SelectReportedUserView: FC<{}> = props =>
else setSelectedUserId(userId);
}, [selectedUserId]);
const back = useCallback(() =>
{
const reportState = Object.assign({}, helpReportState);
reportState.currentStep = --reportState.currentStep;
setHelpReportState(reportState);
}, [helpReportState, setHelpReportState]);
return (
<>
<div className="d-grid col-12 mx-auto justify-content-center">
@ -63,14 +70,17 @@ export const SelectReportedUserView: FC<{}> = props =>
{availableUsers.map((user, index) =>
{
return (
<NitroCardGridItemView key={user.id} onClick={() => selectUser(user.id)} itemActive={( selectedUserId === user.id)}>
<NitroCardGridItemView key={user.id} onClick={() => selectUser(user.id)} itemActive={(selectedUserId === user.id)}>
<span dangerouslySetInnerHTML={{ __html: (user.username) }} />
</NitroCardGridItemView>
)
})}
</NitroCardGridView>
<button className="btn btn-secondary mt-2" type="button" disabled={selectedUserId <= 0} onClick={submitUser}>{LocalizeText('help.emergency.main.submit.button')}</button>
<div className="d-flex gap-2 justify-content-between mt-auto">
<button className="btn btn-secondary mt-2" type="button" onClick={back}>{LocalizeText('generic.back')}</button>
<button className="btn btn-primary mt-2" type="button" disabled={selectedUserId <= 0} onClick={submitUser}>{LocalizeText('help.emergency.main.submit.button')}</button>
</div>
</>
}
</>

View File

@ -6,8 +6,8 @@ import { useHelpContext } from '../context/HelpContext';
export const SelectTopicView: FC<{}> = props =>
{
const { helpReportState = null, setHelpReportState = null } = useHelpContext();
const [ selectedCategory, setSelectedCategory ] = useState(-1);
const [ selectedTopic, setSelectedTopic ] = useState(-1);
const [selectedCategory, setSelectedCategory] = useState(-1);
const [selectedTopic, setSelectedTopic] = useState(-1);
const cfhCategories = useMemo(() =>
{
@ -17,7 +17,7 @@ export const SelectTopicView: FC<{}> = props =>
const submitTopic = useCallback(() =>
{
if(selectedCategory < 0) return;
if(selectedTopic < 0 ) return;
if(selectedTopic < 0) return;
const reportState = Object.assign({}, helpReportState);
reportState.cfhCategory = selectedCategory;
@ -25,29 +25,40 @@ export const SelectTopicView: FC<{}> = props =>
reportState.currentStep = 4;
setHelpReportState(reportState);
}, [cfhCategories, helpReportState, selectedCategory, selectedTopic, setHelpReportState]);
const back = useCallback(() =>
{
const reportState = Object.assign({}, helpReportState);
reportState.currentStep = --reportState.currentStep;
setHelpReportState(reportState);
}, [helpReportState, setHelpReportState]);
return (
<>
<div className="d-grid col-12 mx-auto justify-content-center">
<div className="col-12"><h3 className="fw-bold">{LocalizeText('help.emergency.chat_report.subtitle')}</h3></div>
<div className="text-wrap">{LocalizeText('help.cfh.pick.topic')}</div>
</div>
<div className="d-grid gap-2 col-8 mx-auto overflow-auto">
{ (selectedCategory < 0) &&
cfhCategories.map((category, index) =>
<div className="d-grid gap-2 col-8 mx-auto">
{(selectedCategory < 0) &&
cfhCategories.map((category, index) =>
{
return <button key={index} className="btn btn-danger" type="button" onClick={() => setSelectedCategory(index)}>{LocalizeText(`help.cfh.reason.${category.name}`)}</button>
})
}
{ (selectedCategory >= 0 ) &&
cfhCategories[selectedCategory].topics.map((topic, index) =>
{
return <button key={index} className="btn btn-danger" type="button" onClick={() => setSelectedTopic(index)}>{LocalizeText('help.cfh.topic.' + topic.id)}</button>
})
}
}
{(selectedCategory >= 0) &&
cfhCategories[selectedCategory].topics.map((topic, index) =>
{
return <button key={index} className="btn btn-danger" type="button" onClick={() => setSelectedTopic(index)}>{LocalizeText('help.cfh.topic.' + topic.id)}</button>
})
}
</div>
<div className="d-flex gap-2 justify-content-between mt-auto">
<button className="btn btn-secondary mt-2" type="button" onClick={back}>{LocalizeText('generic.back')}</button>
<button className="btn btn-primary mt-2" type="button" disabled={selectedTopic < 0} onClick={submitTopic}>{LocalizeText('help.emergency.main.submit.button')}</button>
</div>
<button className="btn btn-secondary mt-2" type="button" disabled={selectedTopic < 0} onClick={submitTopic}>{LocalizeText('help.emergency.main.submit.button')}</button>
</>
);
}

View File

@ -1,20 +1,9 @@
.nitro-mod-tools {
width: 200px;
}
.nitro-mod-tools-tickets
{
width: 400px;
.issues
{
height: 200px;
}
}
@import './views/room/room-tools/ModToolsRoomView';
@import './views/chatlog/ChatlogView';
@import './views/user/user-info/ModToolsUserView';
@import './views/user/user-room-visits/ModToolsUserRoomVisitsView';
@import './views/tickets/ModToolsTicketView';

View File

@ -0,0 +1,35 @@
export const getSourceName = (categoryId: number): string =>
{
switch(categoryId)
{
case 1:
case 2:
return 'Normal';
case 3:
return 'Automatic';
case 4:
return 'Automatic IM';
case 5:
return 'Guide System';
case 6:
return 'IM';
case 7:
return 'Room';
case 8:
return 'Panic';
case 9:
return 'Guardian';
case 10:
return 'Automatic Helper';
case 11:
return 'Discussion';
case 12:
return 'Selfie';
case 14:
return 'Photo';
case 15:
return 'Ambassador';
default:
return 'Unknown';
}
}

View File

@ -0,0 +1,11 @@
.nitro-mod-tools-tickets
{
width: 400px;
height: 200px;
}
.nitro-mod-tools-handle-issue
{
width: 400px;
height: 300px;
}

View File

@ -3,6 +3,7 @@ import { FC, useCallback, useMemo, useState } from 'react';
import { GetSessionDataManager } from '../../../../api';
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../layout';
import { useModToolsContext } from '../../context/ModToolsContext';
import { IssueInfoView } from './issue-info/IssueInfoView';
import { ModToolsTicketsViewProps } from './ModToolsTicketsView.types';
import { ModToolsMyIssuesTabView } from './my-issues/ModToolsMyIssuesTabView';
import { ModToolsOpenIssuesTabView } from './open-issues/ModToolsOpenIssuesTabView';
@ -20,11 +21,11 @@ export const ModToolsTicketsView: FC<ModToolsTicketsViewProps> = props =>
const { modToolsState = null } = useModToolsContext();
const { tickets= null } = modToolsState;
const [ currentTab, setCurrentTab ] = useState<number>(0);
const [ issueInfoWindows, setIssueInfoWindows ] = useState<number[]>([]);
const openIssues = useMemo(() =>
{
if(!tickets) return [];
console.log(tickets);
return tickets.filter(issue => issue.state === IssueMessageData.STATE_OPEN);
}, [tickets]);
@ -43,18 +44,44 @@ export const ModToolsTicketsView: FC<ModToolsTicketsViewProps> = props =>
return tickets.filter(issue => issue.state === IssueMessageData.STATE_PICKED);
}, [tickets]);
const onIssueInfoClosed = useCallback((issueId: number) =>
{
const indexOfValue = issueInfoWindows.indexOf(issueId);
if(indexOfValue === -1) return;
const newValues = Array.from(issueInfoWindows);
newValues.splice(indexOfValue, 1);
setIssueInfoWindows(newValues);
}, [issueInfoWindows]);
const onIssueHandleClicked = useCallback((issueId: number) =>
{
if(issueInfoWindows.indexOf(issueId) === -1)
{
const newValues = Array.from(issueInfoWindows);
newValues.push(issueId);
setIssueInfoWindows(newValues);
}
else
{
onIssueInfoClosed(issueId);
}
}, [issueInfoWindows, onIssueInfoClosed]);
const CurrentTabComponent = useCallback(() =>
{
switch(currentTab)
{
case 0: return <ModToolsOpenIssuesTabView openIssues={openIssues}/>;
case 1: return <ModToolsMyIssuesTabView myIssues={myIssues} />;
case 1: return <ModToolsMyIssuesTabView myIssues={myIssues} onIssueHandleClick={onIssueHandleClicked}/>;
case 2: return <ModToolsPickedIssuesTabView pickedIssues={pickedIssues}/>;
default: return null;
}
}, [currentTab, myIssues, openIssues, pickedIssues]);
}, [currentTab, myIssues, onIssueHandleClicked, openIssues, pickedIssues]);
return (
<>
<NitroCardView className="nitro-mod-tools-tickets" simple={ false }>
<NitroCardHeaderView headerText={ 'Tickets' } onCloseClick={ onCloseClick } />
<NitroCardContentView className="p-0 text-black">
@ -71,5 +98,9 @@ export const ModToolsTicketsView: FC<ModToolsTicketsViewProps> = props =>
</div>
</NitroCardContentView>
</NitroCardView>
{
issueInfoWindows && issueInfoWindows.map(issueId => <IssueInfoView key={issueId} issueId={issueId} onIssueInfoClosed={onIssueInfoClosed}/>)
}
</>
);
}

View File

@ -0,0 +1,37 @@
import { CfhChatlogData, CfhChatlogEvent, GetCfhChatlogMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useState } from 'react';
import { CreateMessageHook, SendMessageHook } from '../../../../../hooks';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
import { ChatlogView } from '../../chatlog/ChatlogView';
import { CfhChatlogViewProps } from './CfhChatlogView.types';
export const CfhChatlogView: FC<CfhChatlogViewProps> = props =>
{
const { onCloseClick = null, issueId = null } = props;
const [ chatlogData, setChatlogData ] = useState<CfhChatlogData>(null);
useEffect(() =>
{
SendMessageHook(new GetCfhChatlogMessageComposer(issueId));
}, [issueId]);
const onCfhChatlogEvent = useCallback((event: CfhChatlogEvent) =>
{
const parser = event.getParser();
if(!parser || parser.data.issueId !== issueId) return;
setChatlogData(parser.data);
}, [issueId]);
CreateMessageHook(CfhChatlogEvent, onCfhChatlogEvent);
return (
<NitroCardView className="nitro-mod-tools-cfh-chatlog" simple={true}>
<NitroCardHeaderView headerText={'Issue Chatlog'} onCloseClick={onCloseClick} />
<NitroCardContentView className="text-black">
{ chatlogData && <ChatlogView records={[chatlogData.chatRecord]} />}
</NitroCardContentView>
</NitroCardView>
);
}

View File

@ -0,0 +1,5 @@
export interface CfhChatlogViewProps
{
issueId: number;
onCloseClick(): void;
}

View File

@ -0,0 +1,72 @@
import { CloseIssuesMessageComposer, ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useMemo, useState } from 'react';
import { LocalizeText } from '../../../../../api';
import { ModToolsOpenUserInfoEvent } from '../../../../../events/mod-tools/ModToolsOpenUserInfoEvent';
import { dispatchUiEvent, SendMessageHook } from '../../../../../hooks';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
import { getSourceName } from '../../../common/IssueCategoryNames';
import { useModToolsContext } from '../../../context/ModToolsContext';
import { CfhChatlogView } from './CfhChatlogView';
import { IssueInfoViewProps } from './IssueInfoView.types';
export const IssueInfoView: FC<IssueInfoViewProps> = props =>
{
const { issueId = null, onIssueInfoClosed = null } = props;
const { modToolsState = null } = useModToolsContext();
const { tickets= null } = modToolsState;
const [ cfhChatlogOpen, setcfhChatlogOpen ] = useState(false);
const ticket = useMemo(() =>
{
return tickets.find( issue => issue.issueId === issueId);
}, [issueId, tickets]);
const onReleaseIssue = useCallback((issueId: number) =>
{
SendMessageHook(new ReleaseIssuesMessageComposer([issueId]));
onIssueInfoClosed(issueId);
}, [onIssueInfoClosed]);
const openUserInfo = useCallback((userId: number) =>
{
dispatchUiEvent(new ModToolsOpenUserInfoEvent(userId));
}, []);
const closeIssue = useCallback((resolutionType: number) =>
{
SendMessageHook(new CloseIssuesMessageComposer([issueId], resolutionType));
onIssueInfoClosed(issueId)
}, [issueId, onIssueInfoClosed]);
return (
<>
<NitroCardView className="nitro-mod-tools-handle-issue" simple={true}>
<NitroCardHeaderView headerText={'Resolving issue ' + issueId} onCloseClick={() => onIssueInfoClosed(issueId)} />
<NitroCardContentView className="text-black">
<div className="row">
<div className="col-8">
<h3>Issue Information</h3>
<div><span className="fw-bold">Source: </span>{getSourceName(ticket.categoryId)}</div>
<div><span className="fw-bold">Category: </span>{LocalizeText('help.cfh.topic.' + ticket.reportedCategoryId)}</div>
<div><span className="fw-bold">Description: </span>{ticket.message}</div>
<div><span className="fw-bold">Caller: </span><button className="btn btn-link fw-bold" onClick={() => openUserInfo(ticket.reporterUserId)}>{ticket.reporterUserName}</button></div>
<div><span className="fw-bold">Reported User: </span><button className="btn btn-link fw-bold" onClick={() => openUserInfo(ticket.reportedUserId)}>{ticket.reportedUserName}</button></div>
</div>
<div className="col-4">
<div className="d-grid gap-2 mb-4">
<button className="btn btn-secondary" onClick={() => setcfhChatlogOpen(!cfhChatlogOpen)}>Chatlog</button>
</div>
<div className="d-grid gap-2">
<button className="btn btn-primary" onClick={() => closeIssue(CloseIssuesMessageComposer.RESOLUTION_USELESS)}>Close as useless</button>
<button className="btn btn-danger" onClick={() => closeIssue(CloseIssuesMessageComposer.RESOLUTION_ABUSIVE)}>Close as abusive</button>
<button className="btn btn-success" onClick={() => closeIssue(CloseIssuesMessageComposer.RESOLUTION_RESOLVED)}>Close as resolved</button>
<button className="btn btn-secondary" onClick={() => onReleaseIssue(issueId)}>Release</button>
</div>
</div>
</div>
</NitroCardContentView>
</NitroCardView>
{ cfhChatlogOpen && <CfhChatlogView issueId={issueId} onCloseClick={() => setcfhChatlogOpen(false) }/>}
</>
);
}

View File

@ -0,0 +1,5 @@
export interface IssueInfoViewProps
{
issueId: number;
onIssueInfoClosed(issueId: number): void;
}

View File

@ -1,53 +1,45 @@
import { FC } from 'react';
import { AutoSizer, List, ListRowProps, ListRowRenderer } from 'react-virtualized';
import { ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback } from 'react';
import { SendMessageHook } from '../../../../../hooks';
import { ModToolsMyIssuesTabViewProps } from './ModToolsMyIssuesTabView.types';
export const ModToolsMyIssuesTabView: FC<ModToolsMyIssuesTabViewProps> = props =>
{
const { myIssues = null } = props;
const RowRenderer: ListRowRenderer = (props: ListRowProps) =>
{
const item = myIssues[props.index];
return (
<div key={props.key} style={props.style} className="row issue-entry justify-content-start">
<div className="col-auto text-center">{item.categoryId}</div>
<div className="col justify-content-start username"><span className="fw-bold cursor-pointer">{item.reportedUserName}</span></div>
<div className="col-sm-2 justify-content-start"><span className="text-break text-wrap h-100">{item.getOpenTime(new Date().getTime())}</span></div>
<div className="col-sm-2">
<button className="btn btn-sm btn-primary">View Issue</button>
</div>
</div>
);
};
const { myIssues = null, onIssueHandleClick = null } = props;
const onReleaseIssue = useCallback((issueId: number) =>
{
SendMessageHook(new ReleaseIssuesMessageComposer([issueId]));
}, []);
return (
<>
<div className="row align-items-start w-100">
<div className="col-auto text-center fw-bold">Type</div>
<div className="col fw-bold">Room/Player</div>
<div className="col-sm-2 fw-bold">Opened</div>
<div className="col-sm-2"></div>
</div>
<div className="row w-100 issues">
<AutoSizer defaultWidth={400} defaultHeight={200}>
{({ height, width }) =>
{
return (
<List
width={width}
height={height}
rowCount={myIssues.length}
rowHeight={25}
className={'issues-container'}
rowRenderer={RowRenderer}
/>
)
}
}
</AutoSizer>
</div>
<table className="table text-black table-striped">
<thead>
<tr>
<th scope="col">Type</th>
<th scope="col">Room/Player</th>
<th scope="col">Opened</th>
<th scope="col"></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{myIssues.map(issue =>
{
return (
<tr className="text-black" key={issue.issueId}>
<td>{issue.categoryId}</td>
<td>{issue.reportedUserName}</td>
<td>{new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString()}</td>
<td><button className="btn btn-sm btn-primary" onClick={() => onIssueHandleClick(issue.issueId)}>Handle</button></td>
<td><button className="btn btn-sm btn-danger" onClick={() => onReleaseIssue(issue.issueId)}>Release</button></td>
</tr>)
})
}
</tbody>
</table>
</>
);
}

View File

@ -3,4 +3,5 @@ import { IssueMessageData } from '@nitrots/nitro-renderer';
export interface ModToolsMyIssuesTabViewProps
{
myIssues: IssueMessageData[];
onIssueHandleClick(issueId: number): void;
}

View File

@ -1,53 +1,42 @@
import { FC } from 'react';
import { AutoSizer, List, ListRowProps, ListRowRenderer } from 'react-virtualized';
import { PickIssuesMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback } from 'react';
import { SendMessageHook } from '../../../../../hooks';
import { ModToolsOpenIssuesTabViewProps } from './ModToolsOpenIssuesTabView.types';
export const ModToolsOpenIssuesTabView: FC<ModToolsOpenIssuesTabViewProps> = props =>
{
const { openIssues = null } = props;
const RowRenderer: ListRowRenderer = (props: ListRowProps) =>
const onPickIssue = useCallback((issueId: number) =>
{
const item = openIssues[props.index];
SendMessageHook(new PickIssuesMessageComposer([issueId], false, 0, 'pick issue button'));
}, []);
return (
<div key={props.key} style={props.style} className="row issue-entry justify-content-start">
<div className="col-auto text-center">{item.categoryId}</div>
<div className="col justify-content-start username"><span className="fw-bold cursor-pointer">{item.reportedUserName}</span></div>
<div className="col-sm-2 justify-content-start"><span className="text-break text-wrap h-100">{item.getOpenTime(new Date().getTime())}</span></div>
<div className="col-sm-2">
<button className="btn btn-sm btn-primary">Pick Issue</button>
</div>
</div>
);
};
return (
<>
<div className="row align-items-start w-100">
<div className="col-auto text-center fw-bold">Type</div>
<div className="col fw-bold">Room/Player</div>
<div className="col-sm-2 fw-bold">Opened</div>
<div className="col-sm-2"></div>
</div>
<div className="row w-100 issues">
<AutoSizer defaultWidth={400} defaultHeight={200}>
{({ height, width }) =>
{
return (
<List
width={width}
height={height}
rowCount={openIssues.length}
rowHeight={25}
className={'issues-container'}
rowRenderer={RowRenderer}
/>
)
}
}
</AutoSizer>
</div>
<table className="table text-black table-striped">
<thead>
<tr>
<th scope="col">Type</th>
<th scope="col">Room/Player</th>
<th scope="col">Opened</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{openIssues.map(issue =>
{
return (
<tr className="text-black" key={issue.issueId}>
<td>{issue.categoryId}</td>
<td>{issue.reportedUserName}</td>
<td>{new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString()}</td>
<td><button className="btn btn-sm btn-success" onClick={() => onPickIssue(issue.issueId)}>Pick Issue</button></td>
</tr>)
})
}
</tbody>
</table>
</>
);
}

View File

@ -1,53 +1,35 @@
import { FC } from 'react';
import { AutoSizer, List, ListRowProps, ListRowRenderer } from 'react-virtualized';
import { ModToolsPickedIssuesTabViewProps } from './ModToolsPickedIssuesTabView.types';
export const ModToolsPickedIssuesTabView: FC<ModToolsPickedIssuesTabViewProps> = props =>
{
const { pickedIssues = null } = props;
const RowRenderer: ListRowRenderer = (props: ListRowProps) =>
{
const item = pickedIssues[props.index];
return (
<div key={props.key} style={props.style} className="row issue-entry justify-content-start">
<div className="col-auto text-center">{item.categoryId}</div>
<div className="col justify-content-start username"><span className="fw-bold cursor-pointer">{item.reportedUserName}</span></div>
<div className="col-sm-2 justify-content-start"><span className="text-break text-wrap h-100">{item.getOpenTime(new Date().getTime())}</span></div>
<div className="col-sm-2">
{item.pickerUserName}
</div>
</div>
);
};
return (
<>
<div className="row align-items-start w-100">
<div className="col-auto text-center fw-bold">Type</div>
<div className="col fw-bold">Room/Player</div>
<div className="col-sm-2 fw-bold">Opened</div>
<div className="col-sm-2 fw-bold">Picker</div>
</div>
<div className="row w-100 issues">
<AutoSizer defaultWidth={400} defaultHeight={200}>
{({ height, width }) =>
{
return (
<List
width={width}
height={height}
rowCount={pickedIssues.length}
rowHeight={25}
className={'issues-container'}
rowRenderer={RowRenderer}
/>
)
}
}
</AutoSizer>
</div>
<table className="table text-black table-striped">
<thead>
<tr>
<th scope="col">Type</th>
<th scope="col">Room/Player</th>
<th scope="col">Opened</th>
<th scope="col">Picker</th>
</tr>
</thead>
<tbody>
{pickedIssues.map(issue =>
{
return (
<tr className="text-black" key={issue.issueId}>
<td>{issue.categoryId}</td>
<td>{issue.reportedUserName}</td>
<td>{new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString()}</td>
<td>{issue.pickerUserName}</td>
</tr>)
})
}
</tbody>
</table>
</>
);
}