✨ Add Vue 3 composable for group sizing and print-safe layout generation
This commit is contained in:
@@ -0,0 +1,165 @@
|
|||||||
|
/**
|
||||||
|
* useGroupPrintLayout
|
||||||
|
* --------------------------------------------------
|
||||||
|
* Vue 3 composable for group sizing and
|
||||||
|
* print-safe layout generation.
|
||||||
|
*
|
||||||
|
* Uses only Vue reactivity + vanilla JS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {number} options.rowsPerPage
|
||||||
|
* @param {number} options.headerRows
|
||||||
|
* @param {number} options.spacerRows
|
||||||
|
*/
|
||||||
|
export function useGroupPrintLayout(options) {
|
||||||
|
// -----------------------------
|
||||||
|
// Reactive state
|
||||||
|
// -----------------------------
|
||||||
|
const players = ref([]);
|
||||||
|
const groupIds = ref([]);
|
||||||
|
const droppedPlayerIds = ref([]);
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// Internal helpers (pure JS)
|
||||||
|
// -----------------------------
|
||||||
|
function calculateGroupSizes(totalPlayers, ids) {
|
||||||
|
const baseSize = Math.floor(totalPlayers / ids.length);
|
||||||
|
const remainder = totalPlayers % ids.length;
|
||||||
|
|
||||||
|
return ids.map((id, index) => ({
|
||||||
|
id,
|
||||||
|
players: [],
|
||||||
|
size: baseSize + (index < remainder ? 1 : 0)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignPlayersToGroups(playerList, groups) {
|
||||||
|
let index = 0;
|
||||||
|
|
||||||
|
groups.forEach(group => {
|
||||||
|
for (let i = 0; i < group.size; i++) {
|
||||||
|
if (index >= playerList.length) break;
|
||||||
|
|
||||||
|
const player = { ...playerList[index] };
|
||||||
|
player.groupId = group.id;
|
||||||
|
group.players.push(player);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyDrops(groups, dropIds) {
|
||||||
|
if (!dropIds.length) return groups;
|
||||||
|
|
||||||
|
const dropSet = new Set(dropIds);
|
||||||
|
groups.forEach(group => {
|
||||||
|
group.players = group.players.filter(player => !dropSet.has(player.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateGroupPrintRows(group) {
|
||||||
|
return options.headerRows + group.players.length + options.spacerRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePrintLayout(groups) {
|
||||||
|
const pages = [];
|
||||||
|
let pageNumber = 1;
|
||||||
|
|
||||||
|
let currentPage = {
|
||||||
|
pageNumber,
|
||||||
|
groups: [],
|
||||||
|
usedRows: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
groups.forEach(group => {
|
||||||
|
const groupRows = calculateGroupPrintRows(group);
|
||||||
|
|
||||||
|
if (currentPage.usedRows + groupRows > options.rowsPerPage) {
|
||||||
|
pages.push(currentPage);
|
||||||
|
pageNumber++;
|
||||||
|
|
||||||
|
currentPage = {
|
||||||
|
pageNumber,
|
||||||
|
groups: [],
|
||||||
|
usedRows: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPage.groups.push(group);
|
||||||
|
currentPage.usedRows += groupRows;
|
||||||
|
});
|
||||||
|
|
||||||
|
pages.push(currentPage);
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// Computed pipeline
|
||||||
|
// -----------------------------
|
||||||
|
const groupedData = computed(() => {
|
||||||
|
if (!players.value.length || !groupIds.value.length) return [];
|
||||||
|
|
||||||
|
let groups = calculateGroupSizes(players.value.length, groupIds.value);
|
||||||
|
|
||||||
|
groups = assignPlayersToGroups(players.value, groups);
|
||||||
|
groups = applyDrops(groups, droppedPlayerIds.value);
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
});
|
||||||
|
|
||||||
|
const printPages = computed(() => {
|
||||||
|
if (!groupedData.value.length) return [];
|
||||||
|
return generatePrintLayout(groupedData.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// Public API
|
||||||
|
// -----------------------------
|
||||||
|
function setPlayers(list) {
|
||||||
|
players.value = Array.isArray(list) ? list : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function setGroups(ids) {
|
||||||
|
groupIds.value = Array.isArray(ids) ? ids : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function dropPlayer(id) {
|
||||||
|
if (!droppedPlayerIds.value.includes(id)) {
|
||||||
|
droppedPlayerIds.value.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function restorePlayer(id) {
|
||||||
|
droppedPlayerIds.value = droppedPlayerIds.value.filter(pid => pid !== id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetDrops() {
|
||||||
|
droppedPlayerIds.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// state
|
||||||
|
players,
|
||||||
|
groupIds,
|
||||||
|
droppedPlayerIds,
|
||||||
|
|
||||||
|
// derived
|
||||||
|
groupedData,
|
||||||
|
printPages,
|
||||||
|
|
||||||
|
// actions
|
||||||
|
setPlayers,
|
||||||
|
setGroups,
|
||||||
|
dropPlayer,
|
||||||
|
restorePlayer,
|
||||||
|
resetDrops
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user