7QS7R2ORWCZMTCE3SD5PJ5ZL27N3RS7YCWBHXH3JDPTHDQ3KBAMQC GGREOYIRZE2GH62X7YXQA7IUSSB25ENLQ5OKVJYWPNI462DP37FAC Q4BYGYNX3UZXX47EMX2GWX6M6ICSMIMCLQ7HVOYS4HB4RHYI4NEQC HXRDRHIVGSBEAMTKJFZK43MSB53BKON6F77AARUQNWFUPSTZSBPAC 62OPHDLT2IIHK2OEH76NWB6V7E2Z5USUBNHB4X3DP2XLDI3YHY7QC N7TZV5WJKLYKSG6SIVAID4ZCC76YHZU5U5WWQGG6TFKAV6CE3Y6AC 3QZU4ZIBA24ZXKPBGMTWX4S2GF6AOXJMPD2753CCTU7QUL2WMDXAC EKERBH2GMXCDZSFXAQP6FGGQTTW3ZENOUM4ZKZVHWAAKGW64YLTAC export const StatBlocksDispatch = React.createContext<any>(null);export const ENCOUNTER_NAMES: { [id: number]: string } = {2398: 'Shriekwing',2418: 'Huntsman Altimor',2383: 'Hungering Destroyer',2402: 'Sun King\'s Salvation',2405: 'Artificer Xy\'mox',2406: 'Lady Inerva Darkvein',2412: 'The Council of Blood',2399: 'Sludgefist',2417: 'Stone Legion Generals',2407: 'Sire Denathrius',};export const DIFFICULTY_NAMES: { [id: number]: string } = {5: 'Mythic',};
const dateformat = {weekday: 'long',month: 'long',day: 'numeric'};export function formatDate(date: Date): string {return new Intl.DateTimeFormat('en-US', dateformat).format(date);}export function formatShortDate(date: Date): string {return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric' }).format(date);}export function formatDuration(dur_ms: number, suffix: string = ' hours'): string {return `${(dur_ms / 1000 / 60 / 60).toFixed(1)}${suffix}`;}
const ENCOUNTER_NAMES: { [id: number]: string } = {2398: 'Shriekwing',2418: 'Huntsman Altimor',2383: 'Hungering Destroyer',2402: 'Sun King\'s Salvation',2405: 'Artificer Xy\'mox',2406: 'Lady Inerva Darkvein',2412: 'The Council of Blood',2399: 'Sludgefist',2417: 'Stone Legion Generals',2407: 'Sire Denathrius',};const DIFFICULTY_NAMES: { [id: number]: string } = {5: 'Mythic',};
function formatDuration(dur_ms: number): string {return `${(dur_ms / 1000 / 60 / 60).toFixed(1)} hours`;
export interface StatProps {logs: Log[];region: Region;difficulty: number;}export interface EncounterStats {encounterId: number;killLog?: Log;killWeek?: number;ilvlRange: [number, number];pullCount: number;progTime: number;
const pullCount = props.logs.reduce((count, log) => count + log.fights.length, 0);const progTime = props.logs.reduce((time, log) => {const duration_ms = log.fights[log.fights.length - 1].endTime - log.fights[0].startTime;return time + duration_ms;}, 0);const killWeek = killLog ? tierWeek(killLog.zone, killLog.startTime, props.difficulty === 5, props.region) : undefined;return {encounterId: props.logs[0].fights[0].encounterId,killLog, ilvlRange: ilvlRange as [number, number], pullCount, progTime, killWeek}}function EncounterStatColumn(props: StatProps) {const stats = encounter_stats(props);const { killLog, ilvlRange: [minIlvl, maxIlvl], pullCount, progTime, killWeek } = stats;const dispatch = useContext(StatBlocksDispatch);useEffect(() => {if(dispatch) {dispatch(stats);}// the empty dep list is intentional. we do this once on load.// eslint-disable-next-line}, []);
<div className="grid gap-8 mt-4" style={{gridTemplateColumns: "20rem auto"}}><EncounterStats logs={logs} region={props.region} difficulty={props.difficulty} />
<div className="grid gap-8 mt-4 xl:grid-cols-encounter"><EncounterStatColumn logs={logs} region={props.region} difficulty={props.difficulty} />
import EncounterOverview from './EncounterOverview';import { ZONE_ENCOUNTERS, toRegion } from './model';
import EncounterOverview, { EncounterStats } from './EncounterOverview';import { StatBlocksDispatch, ZONE_ENCOUNTERS, toRegion, ENCOUNTER_NAMES, DIFFICULTY_NAMES } from './model';import { formatShortDate, formatDuration } from './format';
type StatBlocks = { [id: number]: EncounterStats };function StatTable({ statblocks, encounters }: { statblocks: StatBlocks, encounters: number[] }) {return (<section className="mb-12"><legend className="text-2xl">Progression Overview</legend><table><thead className="text-center"><tr><th className="pl-2 pr-2 border-b border-gray-200"></th><th className="pl-2 pr-2 border-b border-l border-gray-200">Kill Date</th><th className="pl-2 pr-2 border-b border-gray-200">Kill Week</th><th className="pl-2 pr-2 border-b border-l border-gray-200">Pulls</th><th className="pl-2 pr-2 border-b border-gray-200">Prog Time</th><th className="pl-2 pr-2 border-b border-l border-gray-200">Min ilvl</th><th className="pl-2 pr-2 border-b">Max ilvl</th></tr></thead><tbody className="text-right">{encounters.map(id => {const stats = statblocks[id];return (<tr key={id}><td className="text-left pr-2 border-r border-gray-200">{ENCOUNTER_NAMES[id]}</td><td className="pl-2 pr-2 text-center">{(stats && stats.killLog) ? formatShortDate(stats.killLog.startTime) : ''}</td><td className="pl-2 pr-2 text-center">{(stats && stats.killWeek) ? `Week ${stats.killWeek}` : ''}</td><td className="pl-2 pr-2 border-l border-gray-200">{stats ? stats.pullCount : ''}</td><td className="pl-2 pr-2">{stats ? formatDuration(stats.progTime, ' hrs') : ''}</td><td className="pl-2 pr-2 border-l border-gray-200">{stats ? stats.ilvlRange[0].toFixed(1) : ''}</td><td className="pl-2 pr-2">{stats ? stats.ilvlRange[1].toFixed(1) : ''}</td></tr>);})}</tbody></table></section>);}
} else if(encounters && team.id) {header = <>{Object.values(statblocks).reduce((count, { killLog }) => count + (killLog ? 1 : 0), 0)}/{encounters.length} {DIFFICULTY_NAMES[5][0]} on </>;content = (<><div className="grid xl:grid-cols-2"><section><legend className="text-2xl">Team Overview</legend></section><StatTable encounters={encounters} statblocks={statblocks} /></div>{encounters.map(encounter => (<StatBlocksDispatch.Provider value={dispatch} key={encounter}><EncounterOverview encounterId={encounter} difficulty={5} team={team.id} region={toRegion(region)} /></StatBlocksDispatch.Provider>))}</>);