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 { GetNitroInstance, GetRoomEngine, GetSessionDataManager, IsOwnerOfFurniture } from '../../../..';
import { InventoryTradeRequestEvent, WiredSelectObjectEvent } from '../../../../../events'; import { InventoryTradeRequestEvent, WiredSelectObjectEvent } from '../../../../../events';
import { FriendsSendFriendRequestEvent } from '../../../../../events/friends/FriendsSendFriendRequestEvent'; import { FriendsSendFriendRequestEvent } from '../../../../../events/friends/FriendsSendFriendRequestEvent';
import { HelpReportUserEvent } from '../../../../../events/help/HelpReportUserEvent';
import { dispatchUiEvent } from '../../../../../hooks/events'; import { dispatchUiEvent } from '../../../../../hooks/events';
import { SendMessageHook } from '../../../../../hooks/messages'; import { SendMessageHook } from '../../../../../hooks/messages';
import { PetSupplementEnum } from '../../../../../views/room/widgets/avatar-info/common/PetSupplementEnum'; import { PetSupplementEnum } from '../../../../../views/room/widgets/avatar-info/common/PetSupplementEnum';
@ -164,6 +165,7 @@ export class RoomWidgetInfostandHandler extends RoomWidgetHandler
case RoomWidgetUserActionMessage.REPORT: case RoomWidgetUserActionMessage.REPORT:
return; return;
case RoomWidgetUserActionMessage.REPORT_CFH_OTHER: case RoomWidgetUserActionMessage.REPORT_CFH_OTHER:
dispatchUiEvent(new HelpReportUserEvent(userId));
return; return;
case RoomWidgetUserActionMessage.AMBASSADOR_ALERT_USER: case RoomWidgetUserActionMessage.AMBASSADOR_ALERT_USER:
this.container.roomSession.sendAmbassadorAlertMessage(userId); 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'; public static REPORT_USER: string = 'HCE_HELP_CENTER_REPORT_USER';
private _reportedUserId: number; private _reportedUserId: number;
private _reportedUsername: string;
constructor(userId: number, username: string) constructor(userId: number)
{ {
super(HelpReportUserEvent.REPORT_USER); super(HelpReportUserEvent.REPORT_USER);
this._reportedUserId = userId; this._reportedUserId = userId;
this._reportedUsername = username;
} }
public get reportedUserId(): number public get reportedUserId(): number
{ {
return this._reportedUserId; return this._reportedUserId;
} }
public get reportedUsername(): string
{
return this._reportedUsername;
}
} }

View File

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

View File

@ -45,14 +45,25 @@ export const SelectReportedChatsView: FC<{}> = props =>
setHelpReportState(reportState); setHelpReportState(reportState);
}, [helpReportState, selectedChats, setHelpReportState]); }, [helpReportState, selectedChats, setHelpReportState]);
const back = useCallback(() =>
{
const reportState = Object.assign({}, helpReportState);
reportState.currentStep = --reportState.currentStep;
setHelpReportState(reportState);
}, [helpReportState, setHelpReportState]);
return ( return (
<> <>
<div className="d-grid col-12 mx-auto justify-content-center"> <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="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> </div>
{
(userChats.length === 0) && <div>{LocalizeText('help.cfh.error.no_user_data')}</div>
}
{ userChats.length > 0 && { userChats.length > 0 &&
<> <>
<NitroCardGridView columns={1}> <NitroCardGridView columns={1}>
@ -65,7 +76,11 @@ export const SelectReportedChatsView: FC<{}> = props =>
) )
})} })}
</NitroCardGridView> </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 { RoomObjectType } from '@nitrots/nitro-renderer';
import { FC, useCallback, useMemo, useState } from 'react'; import { FC, useCallback, useMemo, useState } from 'react';
import { LocalizeText } from '../../../api'; import { GetSessionDataManager, LocalizeText } from '../../../api';
import { NitroCardGridItemView, NitroCardGridView } from '../../../layout'; import { NitroCardGridItemView, NitroCardGridView } from '../../../layout';
import { GetChatHistory } from '../../chat-history/common/GetChatHistory'; import { GetChatHistory } from '../../chat-history/common/GetChatHistory';
import { ChatEntryType } from '../../chat-history/context/ChatHistoryContext.types'; import { ChatEntryType } from '../../chat-history/context/ChatHistoryContext.types';
@ -19,7 +19,7 @@ export const SelectReportedUserView: FC<{}> = props =>
GetChatHistory().chats GetChatHistory().chats
.forEach(chat => .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)) if(!users.has(chat.entityId))
{ {
@ -47,6 +47,13 @@ export const SelectReportedUserView: FC<{}> = props =>
else setSelectedUserId(userId); else setSelectedUserId(userId);
}, [selectedUserId]); }, [selectedUserId]);
const back = useCallback(() =>
{
const reportState = Object.assign({}, helpReportState);
reportState.currentStep = --reportState.currentStep;
setHelpReportState(reportState);
}, [helpReportState, setHelpReportState]);
return ( return (
<> <>
<div className="d-grid col-12 mx-auto justify-content-center"> <div className="d-grid col-12 mx-auto justify-content-center">
@ -63,14 +70,17 @@ export const SelectReportedUserView: FC<{}> = props =>
{availableUsers.map((user, index) => {availableUsers.map((user, index) =>
{ {
return ( 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) }} /> <span dangerouslySetInnerHTML={{ __html: (user.username) }} />
</NitroCardGridItemView> </NitroCardGridItemView>
) )
})} })}
</NitroCardGridView> </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 => export const SelectTopicView: FC<{}> = props =>
{ {
const { helpReportState = null, setHelpReportState = null } = useHelpContext(); const { helpReportState = null, setHelpReportState = null } = useHelpContext();
const [ selectedCategory, setSelectedCategory ] = useState(-1); const [selectedCategory, setSelectedCategory] = useState(-1);
const [ selectedTopic, setSelectedTopic ] = useState(-1); const [selectedTopic, setSelectedTopic] = useState(-1);
const cfhCategories = useMemo(() => const cfhCategories = useMemo(() =>
{ {
@ -17,7 +17,7 @@ export const SelectTopicView: FC<{}> = props =>
const submitTopic = useCallback(() => const submitTopic = useCallback(() =>
{ {
if(selectedCategory < 0) return; if(selectedCategory < 0) return;
if(selectedTopic < 0 ) return; if(selectedTopic < 0) return;
const reportState = Object.assign({}, helpReportState); const reportState = Object.assign({}, helpReportState);
reportState.cfhCategory = selectedCategory; reportState.cfhCategory = selectedCategory;
@ -25,29 +25,40 @@ export const SelectTopicView: FC<{}> = props =>
reportState.currentStep = 4; reportState.currentStep = 4;
setHelpReportState(reportState); setHelpReportState(reportState);
}, [cfhCategories, helpReportState, selectedCategory, selectedTopic, setHelpReportState]); }, [cfhCategories, helpReportState, selectedCategory, selectedTopic, setHelpReportState]);
const back = useCallback(() =>
{
const reportState = Object.assign({}, helpReportState);
reportState.currentStep = --reportState.currentStep;
setHelpReportState(reportState);
}, [helpReportState, setHelpReportState]);
return ( return (
<> <>
<div className="d-grid col-12 mx-auto justify-content-center"> <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="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 className="text-wrap">{LocalizeText('help.cfh.pick.topic')}</div>
</div> </div>
<div className="d-grid gap-2 col-8 mx-auto overflow-auto"> <div className="d-grid gap-2 col-8 mx-auto">
{ (selectedCategory < 0) && {(selectedCategory < 0) &&
cfhCategories.map((category, index) => cfhCategories.map((category, index) =>
{ {
return <button key={index} className="btn btn-danger" type="button" onClick={() => setSelectedCategory(index)}>{LocalizeText(`help.cfh.reason.${category.name}`)}</button> return <button key={index} className="btn btn-danger" type="button" onClick={() => setSelectedCategory(index)}>{LocalizeText(`help.cfh.reason.${category.name}`)}</button>
}) })
} }
{ (selectedCategory >= 0 ) && {(selectedCategory >= 0) &&
cfhCategories[selectedCategory].topics.map((topic, index) => 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> 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> </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 { .nitro-mod-tools {
width: 200px; width: 200px;
}
.nitro-mod-tools-tickets
{
width: 400px;
.issues
{
height: 200px;
}
} }
@import './views/room/room-tools/ModToolsRoomView'; @import './views/room/room-tools/ModToolsRoomView';
@import './views/chatlog/ChatlogView'; @import './views/chatlog/ChatlogView';
@import './views/user/user-info/ModToolsUserView'; @import './views/user/user-info/ModToolsUserView';
@import './views/user/user-room-visits/ModToolsUserRoomVisitsView'; @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 { GetSessionDataManager } from '../../../../api';
import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../layout'; import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView } from '../../../../layout';
import { useModToolsContext } from '../../context/ModToolsContext'; import { useModToolsContext } from '../../context/ModToolsContext';
import { IssueInfoView } from './issue-info/IssueInfoView';
import { ModToolsTicketsViewProps } from './ModToolsTicketsView.types'; import { ModToolsTicketsViewProps } from './ModToolsTicketsView.types';
import { ModToolsMyIssuesTabView } from './my-issues/ModToolsMyIssuesTabView'; import { ModToolsMyIssuesTabView } from './my-issues/ModToolsMyIssuesTabView';
import { ModToolsOpenIssuesTabView } from './open-issues/ModToolsOpenIssuesTabView'; import { ModToolsOpenIssuesTabView } from './open-issues/ModToolsOpenIssuesTabView';
@ -20,11 +21,11 @@ export const ModToolsTicketsView: FC<ModToolsTicketsViewProps> = props =>
const { modToolsState = null } = useModToolsContext(); const { modToolsState = null } = useModToolsContext();
const { tickets= null } = modToolsState; const { tickets= null } = modToolsState;
const [ currentTab, setCurrentTab ] = useState<number>(0); const [ currentTab, setCurrentTab ] = useState<number>(0);
const [ issueInfoWindows, setIssueInfoWindows ] = useState<number[]>([]);
const openIssues = useMemo(() => const openIssues = useMemo(() =>
{ {
if(!tickets) return []; if(!tickets) return [];
console.log(tickets);
return tickets.filter(issue => issue.state === IssueMessageData.STATE_OPEN); return tickets.filter(issue => issue.state === IssueMessageData.STATE_OPEN);
}, [tickets]); }, [tickets]);
@ -43,18 +44,44 @@ export const ModToolsTicketsView: FC<ModToolsTicketsViewProps> = props =>
return tickets.filter(issue => issue.state === IssueMessageData.STATE_PICKED); return tickets.filter(issue => issue.state === IssueMessageData.STATE_PICKED);
}, [tickets]); }, [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(() => const CurrentTabComponent = useCallback(() =>
{ {
switch(currentTab) switch(currentTab)
{ {
case 0: return <ModToolsOpenIssuesTabView openIssues={openIssues}/>; 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}/>; case 2: return <ModToolsPickedIssuesTabView pickedIssues={pickedIssues}/>;
default: return null; default: return null;
} }
}, [currentTab, myIssues, openIssues, pickedIssues]); }, [currentTab, myIssues, onIssueHandleClicked, openIssues, pickedIssues]);
return ( return (
<>
<NitroCardView className="nitro-mod-tools-tickets" simple={ false }> <NitroCardView className="nitro-mod-tools-tickets" simple={ false }>
<NitroCardHeaderView headerText={ 'Tickets' } onCloseClick={ onCloseClick } /> <NitroCardHeaderView headerText={ 'Tickets' } onCloseClick={ onCloseClick } />
<NitroCardContentView className="p-0 text-black"> <NitroCardContentView className="p-0 text-black">
@ -71,5 +98,9 @@ export const ModToolsTicketsView: FC<ModToolsTicketsViewProps> = props =>
</div> </div>
</NitroCardContentView> </NitroCardContentView>
</NitroCardView> </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 { ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer';
import { AutoSizer, List, ListRowProps, ListRowRenderer } from 'react-virtualized'; import { FC, useCallback } from 'react';
import { SendMessageHook } from '../../../../../hooks';
import { ModToolsMyIssuesTabViewProps } from './ModToolsMyIssuesTabView.types'; import { ModToolsMyIssuesTabViewProps } from './ModToolsMyIssuesTabView.types';
export const ModToolsMyIssuesTabView: FC<ModToolsMyIssuesTabViewProps> = props => export const ModToolsMyIssuesTabView: FC<ModToolsMyIssuesTabViewProps> = props =>
{ {
const { myIssues = null } = props; const { myIssues = null, onIssueHandleClick = 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 onReleaseIssue = useCallback((issueId: number) =>
{
SendMessageHook(new ReleaseIssuesMessageComposer([issueId]));
}, []);
return ( return (
<> <>
<div className="row align-items-start w-100"> <table className="table text-black table-striped">
<div className="col-auto text-center fw-bold">Type</div> <thead>
<div className="col fw-bold">Room/Player</div> <tr>
<div className="col-sm-2 fw-bold">Opened</div> <th scope="col">Type</th>
<div className="col-sm-2"></div> <th scope="col">Room/Player</th>
</div> <th scope="col">Opened</th>
<div className="row w-100 issues"> <th scope="col"></th>
<AutoSizer defaultWidth={400} defaultHeight={200}> <th scope="col"></th>
{({ height, width }) => </tr>
{ </thead>
return ( <tbody>
<List {myIssues.map(issue =>
width={width} {
height={height} return (
rowCount={myIssues.length} <tr className="text-black" key={issue.issueId}>
rowHeight={25} <td>{issue.categoryId}</td>
className={'issues-container'} <td>{issue.reportedUserName}</td>
rowRenderer={RowRenderer} <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>)
} })
</AutoSizer> }
</div> </tbody>
</table>
</> </>
); );
} }

View File

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

View File

@ -1,53 +1,42 @@
import { FC } from 'react'; import { PickIssuesMessageComposer } from '@nitrots/nitro-renderer';
import { AutoSizer, List, ListRowProps, ListRowRenderer } from 'react-virtualized'; import { FC, useCallback } from 'react';
import { SendMessageHook } from '../../../../../hooks';
import { ModToolsOpenIssuesTabViewProps } from './ModToolsOpenIssuesTabView.types'; import { ModToolsOpenIssuesTabViewProps } from './ModToolsOpenIssuesTabView.types';
export const ModToolsOpenIssuesTabView: FC<ModToolsOpenIssuesTabViewProps> = props => export const ModToolsOpenIssuesTabView: FC<ModToolsOpenIssuesTabViewProps> = props =>
{ {
const { openIssues = null } = 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 ( return (
<> <>
<div className="row align-items-start w-100"> <table className="table text-black table-striped">
<div className="col-auto text-center fw-bold">Type</div> <thead>
<div className="col fw-bold">Room/Player</div> <tr>
<div className="col-sm-2 fw-bold">Opened</div> <th scope="col">Type</th>
<div className="col-sm-2"></div> <th scope="col">Room/Player</th>
</div> <th scope="col">Opened</th>
<div className="row w-100 issues"> <th scope="col"></th>
<AutoSizer defaultWidth={400} defaultHeight={200}> </tr>
{({ height, width }) => </thead>
{ <tbody>
return ( {openIssues.map(issue =>
<List {
width={width} return (
height={height} <tr className="text-black" key={issue.issueId}>
rowCount={openIssues.length} <td>{issue.categoryId}</td>
rowHeight={25} <td>{issue.reportedUserName}</td>
className={'issues-container'} <td>{new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString()}</td>
rowRenderer={RowRenderer} <td><button className="btn btn-sm btn-success" onClick={() => onPickIssue(issue.issueId)}>Pick Issue</button></td>
/> </tr>)
) })
} }
} </tbody>
</AutoSizer> </table>
</div>
</> </>
); );
} }

View File

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