/* global React, Icon, Origin, SyncChip, Meter, CB, RoleChip, JobChip, Portrait, Tag, Empty, Seg, Input, useRoute */
// ============================================================
// Screens 5-7: Static roster, Raid night (FFLogs), Loot table
// ============================================================
const { useState: useS57, useMemo: useM57 } = React;
const STATIC_MEMBERS = [
{ name: 'Aerith Nightsong', job: 'PLD', alt: 'WAR', synced: '2h ago' },
{ name: 'Korr Brassbottom', job: 'WAR', alt: 'GNB', synced: '4h ago' },
{ name: 'Liliane d\'Aubreville', job: 'WHM', alt: 'AST', synced: '1d ago' },
{ name: 'Yumi Shiroyama', job: 'SCH', alt: 'SGE', synced: '2h ago' },
{ name: 'Veska Voidsong', job: 'NIN', alt: 'VPR', synced: '3h ago' },
{ name: 'Olric Stonebreak', job: 'SAM', alt: 'DRG', synced: '6h ago' },
{ name: 'Thalia Greycloak', job: 'BRD', alt: 'DNC', synced: '2h ago' },
{ name: 'Imre Kalvain', job: 'BLM', alt: 'PCT', synced: '12h ago' },
];
const SUBS = [
{ name: 'Mira Sundwalker', job: 'AST', alt: 'WHM', synced: '1d ago' },
{ name: 'Brennan Holt', job: 'DRK', alt: 'PLD', synced: '3d ago' },
];
// -------------------- 5. Static — Roster --------------------
function ScreenStaticRoster({ empty }) {
const { go } = useRoute();
if (empty) {
return (
Static
Your raid group's home — roster, schedule, raid nights, loot, prog.
}
title="No static yet"
sub="Create a static to start tracking attendance, prog, and loot. You can invite up to 12 members (8 main + subs)."
action={
Create a static
Join with code
}
/>
);
}
return (
{/* Static header */}
Active
M4S — Savage
Phase 3 prog
Aether Crystals
Tue / Thu · 8:00 – 11:00 PM ET · 4 weeks until tier ends
Avg attendance
94%
last 8 nights ·
Next raid
Tue 8:00 PM
in 2d 14h · auto reminder
Composition · 2 · 2 · 4
go('raidnight')}> Raid nights
go('loot')}> Loot table
Add member
{/* 2-2-4 composition */}
Tanks
2
{STATIC_MEMBERS.filter(m => m.job === 'PLD' || m.job === 'WAR' || m.job === 'DRK' || m.job === 'GNB').map(m => (
))}
Healers
2
{STATIC_MEMBERS.filter(m => ['WHM','SCH','AST','SGE'].includes(m.job)).map(m => (
))}
DPS
4
{STATIC_MEMBERS.filter(m => ['NIN','SAM','BRD','BLM','DRG','MNK','RPR','VPR','MCH','DNC','SMN','RDM','PCT'].includes(m.job)).map(m => (
))}
{/* Subs */}
Subs · {SUBS.length}
Add sub
{SUBS.map(m =>
)}
Open sub slot
);
}
function MemberCard({ m, sub }) {
return (
{m.alt && alt {m.alt} }
{sub && sub }
{m.name}
synced {m.synced}
);
}
// -------------------- 6. Raid Night — FFLogs import --------------------
function ScreenRaidNight({ imported }) {
const { go } = useRoute();
const [link, setLink] = useS57(imported ? 'https://www.fflogs.com/reports/aBC123xYz9KqMnPp' : '');
const [state, setState] = useS57(imported ? 'done' : 'empty'); // empty | importing | done
const attendance = [
{ name: 'Aerith Nightsong', job: 'PLD', present: true, pulls: 18, best: 62, manual: false },
{ name: 'Korr Brassbottom', job: 'WAR', present: true, pulls: 18, best: 62, manual: false },
{ name: 'Liliane d\'Aubreville', job: 'WHM', present: true, pulls: 18, best: 62, manual: false },
{ name: 'Yumi Shiroyama', job: 'SCH', present: true, pulls: 18, best: 62, manual: false },
{ name: 'Veska Voidsong', job: 'NIN', present: true, pulls: 18, best: 62, manual: false },
{ name: 'Olric Stonebreak', job: 'SAM', present: false, pulls: 0, best: 0, manual: true, note: 'Excused' },
{ name: 'Thalia Greycloak', job: 'BRD', present: true, pulls: 18, best: 62, manual: false },
{ name: 'Mira Sundwalker', job: 'AST', present: true, pulls: 12, best: 62, manual: false, isSub: true, swapsFor: 'OLR' },
{ name: 'Imre Kalvain', job: 'BLM', present: true, pulls: 6, best: 41, manual: false, lateNote: 'Joined late · pull 13' },
];
return (
Tuesday · March 4 · 8:00–11:00 PM ET
Raid night
Raid night — M4S phase 3
Paste an FFLogs report below. We'll auto-fill attendance and pull the prog phase from the log.
go('static')}> Back to static
Open loot table
{/* Import bar */}
{state === 'empty' && (
{ setState('importing'); setTimeout(() => setState('done'), 1500); }}>
Import
)}
{state === 'importing' && (
Importing…
)}
{state === 'done' && (
<>
View on FFLogs
{ setState('empty'); setLink(''); }}>
Re-import
>
)}
{state === 'empty' && (
}
title="Paste an FFLogs link to auto-fill attendance"
sub="We'll match each report participant to a member, mark presence, and pull the highest phase reached. You can override any row manually afterward."
action={
Tip: try posting your report to /raid log in Discord — the bot will import it for you.
}
/>
)}
{state !== 'empty' && (
<>
{/* Import summary */}
Phase reached
P3 · 62%
previous best 58% ·
Present
7 / 8
1 absence · 1 sub-in ·
Best pull
62%
pull 14 · 4m 12s
{/* Attendance list */}
Attendance · auto-filled from log
Mark all present
Save night
Present
Member
Job
Pulls
Best phase
Source
{attendance.map(a => (
{}} />
{a.name}
{a.isSub &&
sub-in for {a.swapsFor}
}
{a.lateNote &&
{a.lateNote}
}
{a.note &&
{a.note}
}
{a.pulls}
{a.best ? `${a.best}%` : '—'}
{a.manual
?
: }
))}
Manual overrides are kept separate
Re-importing the same report won't overwrite rows you marked manually. Auto rows still update if FFLogs revises the report.
>
)}
);
}
// -------------------- 7. Static — Loot table --------------------
function ScreenLoot() {
const { go } = useRoute();
const drops = [
{ item: 'Crystalline ring of fending', night: 'Tue · Mar 4', to: 'Aerith Nightsong', toJob: 'PLD', rule: 'BiS priority', bisCount: 9 },
{ item: 'Crystalline trousers of healing', night: 'Tue · Mar 4', to: 'Liliane d\'Aubreville', toJob: 'WHM', rule: 'BiS priority', bisCount: 11 },
{ item: 'Sacred mythril (token)', night: 'Tue · Mar 4', to: 'Korr Brassbottom', toJob: 'WAR', rule: 'Free roll', bisCount: 8 },
{ item: 'Crystalline gloves of striking', night: 'Thu · Feb 27', to: 'Veska Voidsong', toJob: 'NIN', rule: 'BiS priority', bisCount: 7 },
{ item: 'Crystalline earring of casting', night: 'Thu · Feb 27', to: 'Imre Kalvain', toJob: 'BLM', rule: 'BiS priority', bisCount: 6 },
{ item: 'Sacred mythril (token)', night: 'Thu · Feb 27', to: 'Thalia Greycloak', toJob: 'BRD', rule: 'Free roll', bisCount: 5 },
{ item: 'Crystalline shoes of aiming', night: 'Tue · Feb 25', to: 'Olric Stonebreak', toJob: 'SAM', rule: 'BiS priority', bisCount: 4 },
];
const needs = [
{ member: 'Aerith Nightsong', job: 'PLD', remaining: 3, items: ['Earring','Bracelet','Ring'] },
{ member: 'Korr Brassbottom', job: 'WAR', remaining: 5, items: ['MH','Earring','Bracelet','Ring','Necklace'] },
{ member: 'Liliane d\'Aubreville', job: 'WHM', remaining: 1, items: ['Necklace'] },
{ member: 'Yumi Shiroyama', job: 'SCH', remaining: 4, items: ['Body','Hands','Feet','Earring'] },
{ member: 'Veska Voidsong', job: 'NIN', remaining: 4, items: ['Body','Head','Ring','Necklace'] },
{ member: 'Olric Stonebreak', job: 'SAM', remaining: 6, items: ['MH','Head','Body','Hands','Earring','Ring'] },
{ member: 'Thalia Greycloak', job: 'BRD', remaining: 5, items: ['MH','Head','Body','Earring','Necklace'] },
{ member: 'Imre Kalvain', job: 'BLM', remaining: 6, items: ['MH','Head','Body','Hands','Earring','Necklace'] },
];
return (
Aether Crystals · M4S
Tier 2 of 3
Loot table
Who got what, when, and under what rule. All entries are manual — including the bot's `/loot` records.
Export CSV
Add drop
{/* Discord tip callout */}
Log drops live in Discord
Run /loot <item> <member> <rule> in your raid channel — the bot pushes the row straight into this table.
Open Discord →
{/* Drops table */}
Recent drops
{drops.length} drops this tier
} placeholder="Search item or member…" style={{ width: 220 }} />
Filter
Item
Raid night
Awarded to
Rule
Source
{drops.map((d, i) => (
{d.item.includes('token') ? : }
{d.night}
{d.rule}
))}
{/* Who still needs what */}
Computed by diffing each member's BiS list against awarded drops.
);
}
Object.assign(window, { ScreenStaticRoster, ScreenRaidNight, ScreenLoot });