mirror of
https://github.com/Spythere/stacjownik.git
synced 2026-05-03 05:18:11 +00:00
feat: displaying number propositions for selected category in driver view
This commit is contained in:
+1
-1
@@ -3,5 +3,5 @@
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "none"
|
||||
"trailingComma": "none",
|
||||
}
|
||||
|
||||
@@ -1,73 +1,323 @@
|
||||
<template>
|
||||
<div class="driver-train-card">
|
||||
<TrainInfo :train="chosenTrain" :extended="true" />
|
||||
<div class="driver-train-card">
|
||||
<TrainInfo :train="chosenTrain" :extended="true" />
|
||||
|
||||
<!-- Train action buttons -->
|
||||
<div class="train-stock-actions">
|
||||
<button class="btn btn--action" style="margin: 1em 0" @click="copyStockToClipboard()">
|
||||
<i class="fa-regular fa-copy"></i> {{ i18n.t('trains.stock-copy') }}
|
||||
</button>
|
||||
<!-- Train action buttons -->
|
||||
<div class="train-stock-actions">
|
||||
<button class="btn btn--action" style="margin: 1em 0" @click="copyStockToClipboard()">
|
||||
<i class="fa-regular fa-copy"></i> {{ i18n.t('trains.stock-copy') }}
|
||||
</button>
|
||||
|
||||
<button class="btn btn--action" style="margin: 1em 0" @click="showNumberPropositions()">
|
||||
<i class="fa-regular fa-lightbulb"></i> {{ i18n.t('trains.number-propositions') }}
|
||||
</button>
|
||||
<button class="btn btn--action" style="margin: 1em 0" @click="toggleNumberPropositions()">
|
||||
<i class="fa-regular fa-lightbulb"></i> {{ i18n.t('trains.number-propositions') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Proposed numbers container -->
|
||||
<transition name="view-anim" class="propositions-container">
|
||||
<div v-if="arePropositionsVisible">
|
||||
<h3 style="margin-bottom: 0.5em">{{ i18n.t('trains.number-propositions-header') }}</h3>
|
||||
|
||||
<div class="categories-select">
|
||||
<button
|
||||
v-for="(category, i) in availableCategories"
|
||||
class="btn btn--option btn--action"
|
||||
@click="selectCategory(i)"
|
||||
:class="{ checked: i == chosenCategoryIndex }"
|
||||
>
|
||||
{{ category }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<StockList :trainStockList="chosenTrain.stockList" />
|
||||
<TrainSchedule :train="chosenTrain" />
|
||||
</div>
|
||||
<div v-if="numberPropositions.length > 0" class="propositions-numbers">
|
||||
<div v-if="chosenCategory">
|
||||
<b>{{ chosenCategory }} </b> -
|
||||
{{ i18n.t(`categories.${chosenCategory.slice(0, 2)}`) }}
|
||||
({{ i18n.t(`categories.${chosenCategory.slice(2)}`) }})
|
||||
</div>
|
||||
|
||||
<div v-if="chosenCategoryRules">
|
||||
<span v-if="chosenCategoryRules[0]"
|
||||
>{{ i18n.t('trains.number-propositions-third-number') }}
|
||||
<b class="text--primary">{{ chosenCategoryRules[0] }}</b> •
|
||||
</span>
|
||||
|
||||
<span
|
||||
>{{
|
||||
i18n.t('trains.number-propositions-last-nums', {
|
||||
count: chosenCategoryRules[1].length
|
||||
})
|
||||
}}
|
||||
<b class="text--primary">{{ chosenCategoryRules[1] }}</b> -
|
||||
<b class="text--primary">{{ chosenCategoryRules[2] }}</b></span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 0.5em">
|
||||
<b>Propozycje:</b> <i>{{ numberPropositions.join(', ') }}</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="no-propositions" v-else>{{ i18n.t('trains.number-propositions-empty') }}</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<StockList :trainStockList="chosenTrain.stockList" />
|
||||
<TrainSchedule :train="chosenTrain" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue';
|
||||
import { PropType, ref } from 'vue';
|
||||
import { Train } from '../../typings/common';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useApiStore } from '../../store/apiStore';
|
||||
|
||||
import StockList from '../Global/StockList.vue';
|
||||
import TrainSchedule from '../TrainsView/TrainSchedule.vue';
|
||||
import TrainInfo from '../TrainsView/TrainInfo.vue';
|
||||
|
||||
import rulesJSON from '../../data/trainNumberRules.json';
|
||||
import { computed } from 'vue';
|
||||
import { watch } from 'vue';
|
||||
|
||||
const apiStore = useApiStore();
|
||||
|
||||
const i18n = useI18n();
|
||||
|
||||
const arePropositionsVisible = ref(false);
|
||||
const chosenCategoryIndex = ref(0);
|
||||
|
||||
const numberPropositions = ref<string[]>([]);
|
||||
const chosenCategoryRules = ref<any[]>([]);
|
||||
|
||||
const props = defineProps({
|
||||
chosenTrain: {
|
||||
type: Object as PropType<Train>,
|
||||
required: true
|
||||
}
|
||||
chosenTrain: {
|
||||
type: Object as PropType<Train>,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
function copyStockToClipboard() {
|
||||
const stockString = props.chosenTrain.stockList.join(';');
|
||||
const stockString = props.chosenTrain.stockList.join(';');
|
||||
|
||||
if (!stockString) {
|
||||
alert(i18n.t('trains.stock-clipboard-failure'));
|
||||
return;
|
||||
if (!stockString) {
|
||||
alert(i18n.t('trains.stock-clipboard-failure'));
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard
|
||||
.writeText(stockString)
|
||||
.then(() => {
|
||||
prompt(i18n.t('trains.stock-clipboard-success'), stockString);
|
||||
})
|
||||
.catch(() => {
|
||||
alert(i18n.t('trains.stock-clipboard-failure'));
|
||||
});
|
||||
}
|
||||
|
||||
function toggleNumberPropositions() {
|
||||
arePropositionsVisible.value = !arePropositionsVisible.value;
|
||||
|
||||
if (arePropositionsVisible.value) generateNumberPropositions();
|
||||
}
|
||||
|
||||
function selectCategory(i: number) {
|
||||
chosenCategoryIndex.value = i;
|
||||
|
||||
generateNumberPropositions();
|
||||
}
|
||||
|
||||
function generateNumberPropositions() {
|
||||
const categoryCode = chosenCategory.value?.slice(0, 2);
|
||||
const trainNoStr = props.chosenTrain.trainNo.toString();
|
||||
|
||||
// Get category rules
|
||||
const rules = categoryCode
|
||||
? ((rulesJSON.categoriesRules as any)[categoryCode] as any[])
|
||||
: undefined;
|
||||
|
||||
if (!categoryCode || !rules) {
|
||||
numberPropositions.value.length = 0;
|
||||
chosenCategoryRules.value.length = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const [thirdNumber, minRange, maxRange] = rules;
|
||||
|
||||
const propositionsArr: string[] = [];
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
let generatedNumStr = '';
|
||||
|
||||
generatedNumStr += trainNoStr.at(0) ?? Math.floor(Math.random() * 10);
|
||||
generatedNumStr += trainNoStr.at(1) ?? Math.floor(Math.random() * 10);
|
||||
|
||||
// Third number
|
||||
generatedNumStr += thirdNumber ?? '';
|
||||
|
||||
// Remaining numbers
|
||||
const rangeNums = minRange?.length ?? 3;
|
||||
|
||||
const randRange = Math.floor(
|
||||
Math.random() * (Number(maxRange) - Number(minRange)) + Number(minRange)
|
||||
).toString();
|
||||
|
||||
const leadingZeros = new Array(Math.abs(randRange.toString().length - rangeNums))
|
||||
.fill('0')
|
||||
.join('');
|
||||
|
||||
generatedNumStr += `${leadingZeros}${randRange}`;
|
||||
|
||||
const isNumberTaken =
|
||||
apiStore.activeData?.trains?.some((t) => t.trainNo.toString() == generatedNumStr) ?? false;
|
||||
|
||||
if (!isNumberTaken) {
|
||||
propositionsArr.push(generatedNumStr);
|
||||
} else {
|
||||
i--;
|
||||
}
|
||||
|
||||
navigator.clipboard
|
||||
.writeText(stockString)
|
||||
.then(() => {
|
||||
prompt(i18n.t('trains.stock-clipboard-success'), stockString);
|
||||
})
|
||||
.catch(() => {
|
||||
alert(i18n.t('trains.stock-clipboard-failure'));
|
||||
});
|
||||
if (Number(randRange) > Number(maxRange)) break;
|
||||
}
|
||||
|
||||
numberPropositions.value = propositionsArr;
|
||||
chosenCategoryRules.value = rules;
|
||||
}
|
||||
|
||||
function showNumberPropositions() {
|
||||
const chosenCategory = computed(() => {
|
||||
return availableCategories.value.at(chosenCategoryIndex.value);
|
||||
});
|
||||
|
||||
}
|
||||
const availableCategories = computed(() => {
|
||||
const stockList = props.chosenTrain.stockList;
|
||||
const headVehicle = stockList.at(0)?.split('-')[0] ?? '';
|
||||
|
||||
let availableCategories: string[] = [];
|
||||
let categoryTraction = 'E';
|
||||
|
||||
let vehicleTypesSet = new Set<string>();
|
||||
let wagonsNamesSet = new Set<string>();
|
||||
let cargoNamesSet = new Set<string>();
|
||||
|
||||
for (const stockName of stockList) {
|
||||
const [vehicleName, ...cargoList] = stockName.split(':');
|
||||
|
||||
const vehicleData = apiStore.vehiclesData?.find((v) => v.name == vehicleName);
|
||||
|
||||
if (!vehicleData) continue;
|
||||
|
||||
vehicleTypesSet.add(vehicleData.type);
|
||||
|
||||
if (vehicleData.type.startsWith('wagon-')) wagonsNamesSet.add(vehicleData.name.split('_')[0]);
|
||||
|
||||
if (cargoList !== undefined) cargoList.forEach((c) => cargoNamesSet.add(c.split('_')[0]));
|
||||
}
|
||||
|
||||
let vehicleTypesArr = [...vehicleTypesSet];
|
||||
let wagonsNamesArr = [...wagonsNamesSet];
|
||||
|
||||
// Traction
|
||||
if (vehicleTypesArr[0] == 'loco-electric') categoryTraction = 'E';
|
||||
else if (vehicleTypesArr[0] == 'loco-diesel') categoryTraction = 'S';
|
||||
else if (vehicleTypesArr[0] == 'unit-electric') categoryTraction = 'J';
|
||||
else categoryTraction = 'M';
|
||||
|
||||
// EMU / DMU - M*, R*, P*
|
||||
if (vehicleTypesArr.length == 1 && (categoryTraction == 'J' || categoryTraction == 'M')) {
|
||||
availableCategories.push('MO', 'MP', 'MM', 'RO', 'RP', 'RA', 'RM', 'PW');
|
||||
}
|
||||
// Only locos (up to 3) - LT, LP, LS
|
||||
else if (stockList.length <= 3 && vehicleTypesArr.every((v) => v.startsWith('loco-'))) {
|
||||
if (/^(EU|ET|201E|4E|SU|ST|M62|CTLR4C)/.test(headVehicle)) availableCategories.push('LT');
|
||||
if (/^(EU|EP|SU|SP)/.test(headVehicle)) availableCategories.push('LP');
|
||||
if (/^(SM)/.test(headVehicle)) availableCategories.push('LS');
|
||||
}
|
||||
// Only locos (more than 3) - TH
|
||||
else if (stockList.length > 3 && vehicleTypesArr.every((v) => v.startsWith('loco-'))) {
|
||||
availableCategories.push('TH');
|
||||
}
|
||||
// Loco(s) + passenger only wagons - M*, R*, E*, P*
|
||||
else if (vehicleTypesArr.every((v) => v.startsWith('loco-') || v == 'wagon-passenger')) {
|
||||
availableCategories.push('EI', 'EC', 'EN', 'MO', 'MP', 'MM', 'RO', 'RP', 'RA', 'RM', 'PW');
|
||||
}
|
||||
// Loco(s) + cargo only / mixed wagons - T*, Z*
|
||||
else {
|
||||
if (wagonsNamesArr.every((v) => /^(627Z|412Z)/.test(v)))
|
||||
availableCategories.push('TC', 'TD', 'TS');
|
||||
else if (wagonsNamesArr.length < 3 || cargoNamesSet.size < 3)
|
||||
availableCategories.push('TM', 'TG', 'TS');
|
||||
else availableCategories.push('TN', 'TK', 'TR', 'TS', 'ZU', 'ZN');
|
||||
}
|
||||
|
||||
return availableCategories.map((c) => `${c}${categoryTraction}`);
|
||||
});
|
||||
|
||||
watch(
|
||||
computed(() => `${props.chosenTrain.trainNo}`),
|
||||
() => {
|
||||
chosenCategoryIndex.value = 0;
|
||||
generateNumberPropositions();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use '../../styles/responsive';
|
||||
|
||||
.driver-train-card {
|
||||
padding: 1em;
|
||||
background-color: var(--clr-view-bg);
|
||||
border-radius: 0 0 0.5em 0.5em;
|
||||
padding: 1em;
|
||||
background-color: var(--clr-view-bg);
|
||||
border-radius: 0 0 0.5em 0.5em;
|
||||
}
|
||||
|
||||
.train-stock-actions {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
.propositions-container {
|
||||
margin-bottom: 1em;
|
||||
padding: 0.5em;
|
||||
background-color: #111;
|
||||
}
|
||||
|
||||
.categories-select {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: calc(-0.5em);
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: #aaa;
|
||||
}
|
||||
}
|
||||
|
||||
.propositions-numbers {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.no-propositions {
|
||||
margin-top: 1em;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
@include responsive.smallScreen {
|
||||
.propositions-container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.categories-select {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"regionNumbers": {
|
||||
"Warszawa (1)": 1,
|
||||
"Lublin (2)": 2,
|
||||
"Kraków (3)": 3,
|
||||
"Sosnowiec (4)": 4,
|
||||
"Gdańsk (5)": 5,
|
||||
"Wrocław (6)": 6,
|
||||
"Poznań (7)": 7,
|
||||
"Szczecin (8)": 8,
|
||||
"Rezerwa (9)": 9
|
||||
},
|
||||
"sameRegions": {
|
||||
"Losowy": [
|
||||
10, 11, 19, 91, 93, 97, 99, 20, 22, 29, 30, 33, 39, 40, 44, 49, 94, 50, 55, 59, 90, 95, 96,
|
||||
66, 60, 69, 77, 70, 79, 88, 80, 89, 92, 98
|
||||
],
|
||||
"Warszawa (1)": [10, 11, 19, 91, 93, 97, 99],
|
||||
"Lublin (2)": [20, 22, 29],
|
||||
"Kraków (3)": [30, 33, 39],
|
||||
"Sosnowiec (4)": [40, 44, 49, 94],
|
||||
"Gdańsk (5)": [50, 55, 59, 90, 95, 96],
|
||||
"Wrocław (6)": [66, 60, 69],
|
||||
"Poznań (7)": [77, 70, 79],
|
||||
"Szczecin (8)": [88, 80],
|
||||
"Rezerwa (9)": [89, 92, 98]
|
||||
},
|
||||
"categoriesRules": {
|
||||
"EI": [null, "00", "99"],
|
||||
"EC": [null, "000", "049"],
|
||||
"EN": [null, "000", "049"],
|
||||
|
||||
"RO": [null, "200", "999"],
|
||||
"RP": [null, "050", "169"],
|
||||
"RM": [null, "200", "999"],
|
||||
"RA": [null, "200", "999"],
|
||||
|
||||
"MO": [null, "200", "999"],
|
||||
"MP": [null, "050", "169"],
|
||||
"MM": [null, "001", "049"],
|
||||
"MH": [null, "170", "199"],
|
||||
|
||||
"PW": ["6", "000", "899"],
|
||||
"PX": ["6", "000", "899"],
|
||||
|
||||
"TM": ["4", "000", "899"],
|
||||
"TN": ["3", "000", "899"],
|
||||
"TK": ["3", "000", "899"],
|
||||
"TD": ["2", "000", "899"],
|
||||
"TG": ["1", "000", "899"],
|
||||
"TR": ["1", "000", "899"],
|
||||
"TC": ["0", "000", "899"],
|
||||
"TS": ["5", "000", "899"],
|
||||
"TH": ["5", "000", "899"],
|
||||
|
||||
"LT": ["5", "000", "899"],
|
||||
"LP": ["6", "000", "899"],
|
||||
"LS": ["9", "000", "899"],
|
||||
"LZ": ["9", "000", "899"],
|
||||
|
||||
"ZN": ["9", "000", "899"],
|
||||
"ZU": ["9", "000", "899"]
|
||||
}
|
||||
}
|
||||
|
||||
+8
-2
@@ -395,9 +395,15 @@
|
||||
"driver-not-found-others": "Player {driver} is online as:",
|
||||
"driver-not-found-return": "GO BACK TO THE MAIN SITE",
|
||||
"stock-copy": "COPY THE STOCK",
|
||||
"number-propositions": "PROPOSE NUMBERS",
|
||||
"number-propositions": "PROPOSE NUMBER",
|
||||
"stock-clipboard-success": "Successfully copied the railway stock in a text form to your clipboard!",
|
||||
"stock-clipboard-failure": "Oops! Something happened and the railway stock couldn't be copied to your clipboard! :/"
|
||||
"stock-clipboard-failure": "Oops! Something happened and the railway stock couldn't be copied to your clipboard! :/",
|
||||
|
||||
"number-propositions-header": "Generate number examples for selected category:",
|
||||
"number-propositions-third-number": "Third digit:",
|
||||
"number-propositions-last-nums": "{count} last digits from the range of:",
|
||||
"number-propositions-title": "Propositions:",
|
||||
"number-propositions-empty": "No propositions available for the chosen category! :/"
|
||||
},
|
||||
"train-stats": {
|
||||
"stats-button": "STATISTICS",
|
||||
|
||||
+9
-2
@@ -382,9 +382,16 @@
|
||||
"driver-not-found-others": "Gracz {driver} jest online jako:",
|
||||
"driver-not-found-return": "WRÓĆ NA STRONĘ GŁÓWNĄ",
|
||||
"stock-copy": "SKOPIUJ SKŁAD",
|
||||
"number-propositions": "ZAPROPONUJ NUMERY",
|
||||
"number-propositions": "ZAPROPONUJ NUMER",
|
||||
"stock-clipboard-success": "Pomyślnie skopiowano skład w postaci tekstowej do schowka!",
|
||||
"stock-clipboard-failure": "Ups! Nie udało się skopiować składu do schowka! :/"
|
||||
"stock-clipboard-failure": "Ups! Nie udało się skopiować składu do schowka! :/",
|
||||
|
||||
"number-propositions-header": "Wygeneruj propozycje numerów dla kategorii pociągu:",
|
||||
"number-propositions-third-number": "Trzecia cyfra:",
|
||||
"number-propositions-last-nums": "{count} ostatnie cyfry z przedziału:",
|
||||
"number-propositions-title": "Propozycje:",
|
||||
"number-propositions-empty": "Brak propozycji dla wybranej kategorii! :/"
|
||||
|
||||
},
|
||||
"train-stats": {
|
||||
"stats-button": "STATYSTYKI",
|
||||
|
||||
Reference in New Issue
Block a user