add oc to table, small calc fix, add biome
This commit is contained in:
+47
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"vcs": {
|
||||
"enabled": false,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": false
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"ignore": ["dist"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab"
|
||||
},
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"suspicious": {
|
||||
"noArrayIndexKey": "info"
|
||||
},
|
||||
"style": {
|
||||
"useSelfClosingElements": {
|
||||
"level": "error",
|
||||
"fix": "safe"
|
||||
},
|
||||
"useTemplate": "info"
|
||||
},
|
||||
"security": {
|
||||
"noDangerouslySetInnerHtml": "info"
|
||||
},
|
||||
"a11y": {
|
||||
"noSvgWithoutTitle": "off",
|
||||
"useAltText": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double"
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+165
@@ -15,6 +15,7 @@
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
@@ -424,6 +425,170 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/biome": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz",
|
||||
"integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"bin": {
|
||||
"biome": "bin/biome"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/biome"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@biomejs/cli-darwin-arm64": "1.9.4",
|
||||
"@biomejs/cli-darwin-x64": "1.9.4",
|
||||
"@biomejs/cli-linux-arm64": "1.9.4",
|
||||
"@biomejs/cli-linux-arm64-musl": "1.9.4",
|
||||
"@biomejs/cli-linux-x64": "1.9.4",
|
||||
"@biomejs/cli-linux-x64-musl": "1.9.4",
|
||||
"@biomejs/cli-win32-arm64": "1.9.4",
|
||||
"@biomejs/cli-win32-x64": "1.9.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-arm64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz",
|
||||
"integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-x64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz",
|
||||
"integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz",
|
||||
"integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz",
|
||||
"integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz",
|
||||
"integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64-musl": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz",
|
||||
"integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-arm64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz",
|
||||
"integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-x64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz",
|
||||
"integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
|
||||
|
||||
+4
-2
@@ -6,7 +6,8 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
"preview": "vite preview",
|
||||
"lint": "biome check ."
|
||||
},
|
||||
"dependencies": {
|
||||
"chart.js": "^4.4.8",
|
||||
@@ -26,7 +27,8 @@
|
||||
"prettier": "^3.5.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"vite": "^6.2.5"
|
||||
"vite": "^6.2.5",
|
||||
"@biomejs/biome": "^1.9.4"
|
||||
},
|
||||
"prettier": {
|
||||
"useTabs": true,
|
||||
|
||||
+30
-25
@@ -54,7 +54,7 @@ export const Roster = ({
|
||||
|
||||
const Force = ({ force, onePerPage, colorUserChoice }) => {
|
||||
const { units, factionRules, rules, catalog } = force;
|
||||
var mergedRules = new Map([...factionRules, ...rules]);
|
||||
const mergedRules = new Map([...factionRules, ...rules]);
|
||||
// units with role "Character" are sorted to the top, then "Battleline", then "Other"
|
||||
const sortedUnits = units.sort((a, b) => {
|
||||
if (a.role === "Character" && b.role !== "Character") return -1;
|
||||
@@ -295,7 +295,7 @@ const Unit = ({
|
||||
height: 80,
|
||||
minWidth: "33%",
|
||||
}}
|
||||
></div>
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
height: 80,
|
||||
@@ -308,7 +308,7 @@ const Unit = ({
|
||||
backgroundColor: "var(--primary-color-transparent)",
|
||||
height: 40,
|
||||
}}
|
||||
></div>
|
||||
/>
|
||||
{wavyLine}
|
||||
</div>
|
||||
<div
|
||||
@@ -317,7 +317,7 @@ const Unit = ({
|
||||
backgroundColor: "var(--primary-color-transparent)",
|
||||
height: 40,
|
||||
}}
|
||||
></div>
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
@@ -393,6 +393,7 @@ const Unit = ({
|
||||
<div className="absolute right-[1px] top-[3px] flex items-center gap-1.5">
|
||||
{hasImage && !bgRemoved && (
|
||||
<button
|
||||
type="button"
|
||||
className="button print-display-none border-none bg-[#f0f0f0e6] hover:bg-[#f0f0f0]"
|
||||
style={{
|
||||
padding: "1px 4px",
|
||||
@@ -419,6 +420,7 @@ const Unit = ({
|
||||
}}
|
||||
href={`https://www.google.com/search?udm=2&q=${name}+warhammer+40k+miniature`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Search for image
|
||||
</a>
|
||||
@@ -439,15 +441,15 @@ const Unit = ({
|
||||
posthog?.capture?.("user_uploaded_image", {
|
||||
unit_name: name,
|
||||
});
|
||||
if (e.target.files && e.target.files[0]) {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function (ev) {
|
||||
if (e.target.files?.[0]) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = ((ev) => {
|
||||
setImage(ev.target.result);
|
||||
setBgRemoved(false);
|
||||
// reset position of the image
|
||||
trySettingLocalStorage(`positionX_${name}`, 0, () => {});
|
||||
trySettingLocalStorage(`positionY_${name}`, 0, () => {});
|
||||
}.bind(this);
|
||||
}).bind(this);
|
||||
reader.readAsDataURL(e.target.files[0]);
|
||||
}
|
||||
}}
|
||||
@@ -459,6 +461,7 @@ const Unit = ({
|
||||
</label>
|
||||
{hasImage && (
|
||||
<button
|
||||
type="button"
|
||||
className="button print-display-none border-none bg-[#f0f0f0e6] hover:bg-[#f0f0f0]"
|
||||
style={{
|
||||
padding: "1px 4px",
|
||||
@@ -514,7 +517,7 @@ const Unit = ({
|
||||
/>
|
||||
<OtherAbilities abilities={abilities} />
|
||||
</table>
|
||||
<div style={{ flex: "1" }}></div>
|
||||
<div style={{ flex: "1" }} />
|
||||
<table
|
||||
cellSpacing="0"
|
||||
className="weapons-table"
|
||||
@@ -602,7 +605,7 @@ const backgrounds = {
|
||||
|
||||
const checkAbilitiesForInvul = (abilitiesMap, name) => {
|
||||
const abilities = [...(abilitiesMap?.keys?.() || [])];
|
||||
for (let ability of abilities) {
|
||||
for (const ability of abilities) {
|
||||
if (ability.includes(":")) {
|
||||
// if : then only match if the name is the same
|
||||
if (
|
||||
@@ -762,8 +765,8 @@ const InvulRow = ({ hasInvul }) => {
|
||||
}}
|
||||
>
|
||||
<div className="flex" style={{ gap: "1.2rem", left: 0, top: -16 }}>
|
||||
<FancyBox className={"invisible"}></FancyBox>
|
||||
<FancyBox className={"invisible"}></FancyBox>
|
||||
<FancyBox className={"invisible"} />
|
||||
<FancyBox className={"invisible"} />
|
||||
<FancyShield>{invulWithoutSpecial}</FancyShield>
|
||||
</div>
|
||||
<span
|
||||
@@ -817,7 +820,7 @@ const FactionIcon = ({ catalog }) => {
|
||||
}}
|
||||
>
|
||||
{factionIcons[catalog] ||
|
||||
(catalog.includes("Imperium") ? factionIcons["Imperium"] || "" : "")}
|
||||
(catalog.includes("Imperium") ? factionIcons.Imperium || "" : "")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -1044,7 +1047,7 @@ const Rules = ({ rules }) => {
|
||||
|
||||
const removeInvulnsWithoutSpecialRules = (abilities) => {
|
||||
const filteredAbilities = new Map();
|
||||
for (let [key, value] of abilities) {
|
||||
for (const [key, value] of abilities) {
|
||||
switch (value?.trim()?.toLowerCase().replaceAll(".", "")) {
|
||||
case "2+":
|
||||
if (!key?.toLowerCase().includes("invulnerable save"))
|
||||
@@ -1151,8 +1154,8 @@ const Abilities = ({ abilities }) => {
|
||||
let filteredAbilities = removeInvulnsWithoutSpecialRules(abilities);
|
||||
|
||||
// sort abilites so that "Leader" is always at the bottom, "Supreme Commander" also bottom but before "Leader", same for any invulnerable save
|
||||
let abilitiesForEnd = new Map();
|
||||
for (let [key, value] of filteredAbilities) {
|
||||
const abilitiesForEnd = new Map();
|
||||
for (const [key, value] of filteredAbilities) {
|
||||
if (key.toLowerCase().includes("invulnerable save")) {
|
||||
filteredAbilities.delete(key);
|
||||
abilitiesForEnd.set(key, value);
|
||||
@@ -1176,11 +1179,11 @@ const Abilities = ({ abilities }) => {
|
||||
|
||||
const replacedAbilities = new Map();
|
||||
// in the value of the filtered abilites, make certain keywords bold
|
||||
for (let [key, value] of filteredAbilities) {
|
||||
for (const [key, value] of filteredAbilities) {
|
||||
replacedAbilities.set(key, makeKeywordsBold(value));
|
||||
}
|
||||
|
||||
let keys = [...filteredAbilities.keys()];
|
||||
const keys = [...filteredAbilities.keys()];
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -1206,7 +1209,7 @@ const Abilities = ({ abilities }) => {
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: replacedAbilities.get(ability),
|
||||
}}
|
||||
></span>
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -1215,7 +1218,7 @@ const Abilities = ({ abilities }) => {
|
||||
|
||||
const makeKeywordsBold = (text) => {
|
||||
let newValue = text;
|
||||
for (let keyword of boldKeywords) {
|
||||
for (const keyword of boldKeywords) {
|
||||
newValue = newValue.replace(
|
||||
new RegExp(`\\b${keyword}\\b`, "gi"),
|
||||
`<strong>${keyword.toUpperCase()}</strong>`,
|
||||
@@ -1223,7 +1226,7 @@ const makeKeywordsBold = (text) => {
|
||||
}
|
||||
|
||||
// weaponKeywords could be with [] or without
|
||||
for (let keyword of weaponKeywords) {
|
||||
for (const keyword of weaponKeywords) {
|
||||
newValue = newValue.replace(
|
||||
new RegExp(`\\[${keyword}\\]`, "gi"),
|
||||
`<span style="font-weight: 700; color: var(--primary-color); text-transform: uppercase; line-height: 1.05;">[${keyword.toUpperCase()}]</span>`,
|
||||
@@ -1322,7 +1325,9 @@ const FancyShield = ({ children, className, style }) => {
|
||||
};
|
||||
|
||||
const OtherAbilities = ({ abilities }) => {
|
||||
let keys = [...Object.keys(abilities)?.filter((key) => key !== "Abilities")];
|
||||
const keys = abilities
|
||||
? [...Object.keys(abilities).filter((key) => key !== "Abilities")]
|
||||
: [];
|
||||
return keys.map((key) => {
|
||||
return (
|
||||
<Fragment key={key}>
|
||||
@@ -1333,7 +1338,7 @@ const OtherAbilities = ({ abilities }) => {
|
||||
color: "#fff",
|
||||
}}
|
||||
>
|
||||
<th></th>
|
||||
<th />
|
||||
<th colSpan="7" style={{ textAlign: "left" }}>
|
||||
{key}
|
||||
</th>
|
||||
@@ -1342,7 +1347,7 @@ const OtherAbilities = ({ abilities }) => {
|
||||
<tbody>
|
||||
{[...abilities[key]].map(([name, value]) => (
|
||||
<tr key={name}>
|
||||
<td></td>
|
||||
<td />
|
||||
<td
|
||||
colSpan={7}
|
||||
key={name}
|
||||
@@ -1413,7 +1418,7 @@ const ForceRules = ({ rules, onePerPage }) => {
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: makeKeywordsBold(rules.get(rule)),
|
||||
}}
|
||||
></span>
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
+156
-25
@@ -31,16 +31,6 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
// Calculate total wound count
|
||||
const totalWoundCount = units.reduce((sum, unit) => {
|
||||
const modelCounts = unit.models?.map((model) => model.count) || [1];
|
||||
const totalWounds = unit.modelStats?.reduce((woundSum, stat, index) => {
|
||||
const count = modelCounts[index] || 1;
|
||||
return woundSum + (stat.wounds || 0) * count;
|
||||
}, 0);
|
||||
return sum + totalWounds;
|
||||
}, 0);
|
||||
|
||||
// Prepare data for the bar chart, group by toughness and sum points cost
|
||||
const groupedByToughness = units.reduce((acc, unit) => {
|
||||
const toughness = unit.modelStats?.[0]?.toughness || 0;
|
||||
@@ -56,7 +46,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
||||
|
||||
const groupedChartDataToughness = Object.entries(groupedByToughness)
|
||||
.map(([toughness, { totalPoints, unitNames }]) => ({
|
||||
toughness: parseInt(toughness, 10),
|
||||
toughness: Number.parseInt(toughness, 10),
|
||||
totalPoints,
|
||||
unitNames,
|
||||
}))
|
||||
@@ -77,7 +67,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
||||
|
||||
const groupedChartDataMovement = Object.entries(groupedByMovement)
|
||||
.map(([movement, { totalPoints, unitNames }]) => ({
|
||||
movement: parseInt(movement, 10),
|
||||
movement: Number.parseInt(movement, 10),
|
||||
totalPoints,
|
||||
unitNames,
|
||||
}))
|
||||
@@ -98,7 +88,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
||||
|
||||
const groupedChartDataSave = Object.entries(groupedBySave)
|
||||
.map(([save, { totalPoints, unitNames }]) => ({
|
||||
save: parseInt(save, 10),
|
||||
save: Number.parseInt(save, 10),
|
||||
totalPoints,
|
||||
unitNames,
|
||||
}))
|
||||
@@ -125,7 +115,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
||||
<span className="print-display-none">Don't print this card.</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className={"flex flex-col " + (hide ? "print-display-none" : "")}>
|
||||
<div className={`flex flex-col ${hide ? "print-display-none" : ""}`}>
|
||||
<div
|
||||
className="flex justify-between"
|
||||
style={{
|
||||
@@ -170,6 +160,16 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
||||
>
|
||||
Wounds
|
||||
</div>
|
||||
<div
|
||||
className="table-cell border border-[var(--primary-color)] px-4 py-1 text-right"
|
||||
style={{
|
||||
fontSize: "1.1em",
|
||||
color: "#fff",
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
OC
|
||||
</div>
|
||||
<div
|
||||
className="table-cell border border-[var(--primary-color)] px-4 py-1 text-right"
|
||||
style={{
|
||||
@@ -184,7 +184,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
||||
</div>
|
||||
<div className="table-row-group" style={{ lineHeight: 1.05 }}>
|
||||
{sortedUnits.map((unit, index) => {
|
||||
let { name, cost, models } = unit;
|
||||
const { name, cost, models } = unit;
|
||||
const count =
|
||||
models?.filter((model) => model.count > 0)?.length === 1
|
||||
? models?.[0]?.count || 1
|
||||
@@ -195,7 +195,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
||||
className={`table-row ${index % 2 === 0 ? "" : "bg-[#c4c4c480]"}`}
|
||||
>
|
||||
<div className="table-cell border border-dotted border-[#9e9fa1] px-4 py-1">
|
||||
{count > 1 ? count + "x " : ""}
|
||||
{count > 1 ? `${count}x ` : ""}
|
||||
{name}
|
||||
</div>
|
||||
<div className="table-cell border border-dotted border-[#9e9fa1] px-4 py-1 text-right">
|
||||
@@ -226,10 +226,56 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
||||
unit.models[0],
|
||||
);
|
||||
|
||||
const modelCount = modelName?.count || 1;
|
||||
const modelCount =
|
||||
unit.modelStats?.length === 1
|
||||
? // sum of all counts of the models
|
||||
models?.reduce(
|
||||
(sum, model) => sum + model.count,
|
||||
0,
|
||||
) || 1
|
||||
: modelName?.count || 1;
|
||||
return sum + (stat.wounds || 0) * modelCount;
|
||||
}, 0)}
|
||||
</div>
|
||||
<div className="table-cell border border-dotted border-[#9e9fa1] px-4 py-1 text-right">
|
||||
{unit.modelStats?.reduce((sum, stat, index) => {
|
||||
const modelName = unit.models?.reduce(
|
||||
(bestMatch, model) => {
|
||||
const similarity = (str1, str2) => {
|
||||
let matches = 0;
|
||||
for (
|
||||
let i = 0;
|
||||
i < Math.min(str1.length, str2.length);
|
||||
i++
|
||||
) {
|
||||
if (str1[i] === str2[i]) matches++; // Match only if characters are at the same position
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
const currentSimilarity = similarity(
|
||||
stat.name,
|
||||
model.name,
|
||||
);
|
||||
|
||||
return currentSimilarity >
|
||||
similarity(stat.name, bestMatch.name)
|
||||
? model
|
||||
: bestMatch;
|
||||
},
|
||||
unit.models[0],
|
||||
);
|
||||
|
||||
const modelCount =
|
||||
unit.modelStats?.length === 1
|
||||
? // sum of all counts of the models
|
||||
models?.reduce(
|
||||
(sum, model) => sum + model.count,
|
||||
0,
|
||||
) || 1
|
||||
: modelName?.count || 1;
|
||||
return sum + (stat.oc || 0) * modelCount;
|
||||
}, 0)}
|
||||
</div>
|
||||
<div className="table-cell border border-dotted border-[#9e9fa1] px-4 py-1 text-right">
|
||||
{cost.points} pts
|
||||
</div>
|
||||
@@ -241,7 +287,94 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
||||
Total
|
||||
</div>
|
||||
<div className="table-cell border border-[var(--primary-color)] px-4 py-1 text-right">
|
||||
{totalWoundCount}
|
||||
{/* Sum of all wound values */}
|
||||
{sortedUnits.reduce((sum, unit) => {
|
||||
return (
|
||||
sum +
|
||||
unit.modelStats?.reduce((woundSum, stat, index) => {
|
||||
const modelName = unit.models?.reduce(
|
||||
(bestMatch, model) => {
|
||||
const similarity = (str1, str2) => {
|
||||
let matches = 0;
|
||||
for (
|
||||
let i = 0;
|
||||
i < Math.min(str1.length, str2.length);
|
||||
i++
|
||||
) {
|
||||
if (str1[i] === str2[i]) matches++; // Match only if characters are at the same position
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
const currentSimilarity = similarity(
|
||||
stat.name,
|
||||
model.name,
|
||||
);
|
||||
|
||||
return currentSimilarity >
|
||||
similarity(stat.name, bestMatch.name)
|
||||
? model
|
||||
: bestMatch;
|
||||
},
|
||||
unit.models[0],
|
||||
);
|
||||
|
||||
const modelCount =
|
||||
unit.modelStats?.length === 1
|
||||
? // sum of all counts of the models
|
||||
unit?.models?.reduce(
|
||||
(sum, model) => sum + model.count,
|
||||
0,
|
||||
) || 1
|
||||
: modelName?.count || 1;
|
||||
return woundSum + (stat.wounds || 0) * modelCount;
|
||||
}, 0)
|
||||
);
|
||||
}, 0)}
|
||||
</div>
|
||||
<div className="table-cell border border-[var(--primary-color)] px-4 py-1 text-right">
|
||||
{/* Sum of all OC values */}
|
||||
{sortedUnits.reduce((sum, unit) => {
|
||||
return (
|
||||
sum +
|
||||
unit.modelStats?.reduce((ocSum, stat, index) => {
|
||||
const modelName = unit.models?.reduce(
|
||||
(bestMatch, model) => {
|
||||
const similarity = (str1, str2) => {
|
||||
let matches = 0;
|
||||
for (
|
||||
let i = 0;
|
||||
i < Math.min(str1.length, str2.length);
|
||||
i++
|
||||
) {
|
||||
if (str1[i] === str2[i]) matches++; // Match only if characters are at the same position
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
const currentSimilarity = similarity(
|
||||
stat.name,
|
||||
model.name,
|
||||
);
|
||||
|
||||
return currentSimilarity >
|
||||
similarity(stat.name, bestMatch.name)
|
||||
? model
|
||||
: bestMatch;
|
||||
},
|
||||
unit.models[0],
|
||||
);
|
||||
|
||||
const modelCount =
|
||||
unit.modelStats?.length === 1
|
||||
? // sum of all counts of the models
|
||||
unit?.models?.reduce(
|
||||
(sum, model) => sum + model.count,
|
||||
0,
|
||||
) || 1
|
||||
: modelName?.count || 1;
|
||||
return ocSum + (stat.oc || 0) * modelCount;
|
||||
}, 0)
|
||||
);
|
||||
}, 0)}
|
||||
</div>
|
||||
<div className="table-cell border border-[var(--primary-color)] px-4 py-1 text-right">
|
||||
{sortedUnits.reduce((sum, unit) => sum + unit.cost.points, 0)}{" "}
|
||||
@@ -255,7 +388,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
||||
<ChartComponent
|
||||
data={{
|
||||
labels: groupedChartDataMovement.map(
|
||||
(data) => data.movement + '"',
|
||||
(data) => `${data.movement}"`,
|
||||
),
|
||||
datasets: [
|
||||
{
|
||||
@@ -276,7 +409,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
||||
<ChartComponent
|
||||
data={{
|
||||
labels: groupedChartDataToughness.map(
|
||||
(data) => data.toughness + "",
|
||||
(data) => `${data.toughness}`,
|
||||
),
|
||||
datasets: [
|
||||
{
|
||||
@@ -296,7 +429,7 @@ export const ShortSummaryTable = ({ force, primaryColor, name, points }) => {
|
||||
|
||||
<ChartComponent
|
||||
data={{
|
||||
labels: groupedChartDataSave.map((data) => data.save + "+"),
|
||||
labels: groupedChartDataSave.map((data) => `${data.save}+`),
|
||||
datasets: [
|
||||
{
|
||||
label: "Total Cost (pts)",
|
||||
@@ -356,10 +489,8 @@ const ChartComponent = ({ data, title }) => {
|
||||
tooltip: {
|
||||
displayColors: false,
|
||||
callbacks: {
|
||||
title: function (context) {
|
||||
return "";
|
||||
},
|
||||
label: function (context) {
|
||||
title: (context) => "",
|
||||
label: (context) => {
|
||||
// return unit names for the bar
|
||||
return context.dataset.unitNames[context.dataIndex];
|
||||
},
|
||||
|
||||
@@ -50,8 +50,8 @@ export const Weapons = ({ title, weapons, modelStats, forceRules }) => {
|
||||
))}
|
||||
{weapons.length > 0 && (
|
||||
<tr className="emptyRow">
|
||||
<td style={{ borderTop: "none" }}></td>
|
||||
<td colSpan={7}></td>
|
||||
<td style={{ borderTop: "none" }} />
|
||||
<td colSpan={7} />
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
@@ -158,7 +158,7 @@ const Weapon = ({ weapon, previousWeapon, nextWeapon, isMelee, className }) => {
|
||||
</tr>
|
||||
{abilities && abilities !== "-" && (
|
||||
<tr className={className + " noBorderTop"}>
|
||||
<td style={{ backgroundColor: "#dfe0e2" }}></td>
|
||||
<td style={{ backgroundColor: "#dfe0e2" }} />
|
||||
<td
|
||||
colSpan="7"
|
||||
style={{
|
||||
@@ -180,7 +180,7 @@ const Weapon = ({ weapon, previousWeapon, nextWeapon, isMelee, className }) => {
|
||||
const getWeaponClassNames = (weapons, index) => {
|
||||
let differentColor = false;
|
||||
for (let i = 1; i <= index; i++) {
|
||||
let { selectionName } = weapons[i];
|
||||
const { selectionName } = weapons[i];
|
||||
if (!selectionName && !weapons[i - 1].selectionName) {
|
||||
if (weapons[i].name !== weapons[i - 1].name) {
|
||||
differentColor = !differentColor;
|
||||
|
||||
Reference in New Issue
Block a user