feat(MemberOrders): implement member orders feature
- Added new models and routes for managing member orders and order history. - Updated server.js to include member order routes and sync functionality. - Enhanced frontend with new components and dialogs for viewing and managing orders. - Integrated internationalization support for order-related texts across multiple languages. - Updated navigation and views to include access to the new orders feature, improving user experience.
This commit is contained in:
@@ -22,6 +22,10 @@
|
||||
<span class="dropdown-icon">🏓</span>
|
||||
{{ $t('navigation.clickTtAccount') }}
|
||||
</button>
|
||||
<router-link to="/orders" class="dropdown-item" @click="userDropdownOpen = false">
|
||||
<span class="dropdown-icon">📦</span>
|
||||
{{ $t('navigation.orders') }}
|
||||
</router-link>
|
||||
<button v-if="canManagePermissions" type="button" class="dropdown-item" @click="openUserMenuDialog('PermissionsView', $t('navigation.permissions'))">
|
||||
<span class="dropdown-icon">🔐</span>
|
||||
{{ $t('navigation.permissions') }}
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
>
|
||||
📄
|
||||
</span>
|
||||
<span class="pointer" :title="$t('orders.title')" @click.stop="$emit('open-orders', member)">📦</span>
|
||||
<span class="pointer" @click="$emit('open-tags', member)">ℹ️</span>
|
||||
</div>
|
||||
</li>
|
||||
@@ -136,6 +137,7 @@ export default {
|
||||
'open-notes',
|
||||
'show-pic',
|
||||
'mark-form',
|
||||
'open-orders',
|
||||
'open-tags',
|
||||
'open-quick-add',
|
||||
'open-gallery'
|
||||
|
||||
56
frontend/src/components/MemberOrdersDialog.vue
Normal file
56
frontend/src/components/MemberOrdersDialog.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<BaseDialog
|
||||
:model-value="modelValue"
|
||||
:title="dialogTitle"
|
||||
size="large"
|
||||
:width="1220"
|
||||
max-width="96vw"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
@close="$emit('close')"
|
||||
>
|
||||
<OrdersPanel :club-id="clubId" :member="member" />
|
||||
<template #footer>
|
||||
<button type="button" class="cancel-action" @click="$emit('update:modelValue', false)">
|
||||
{{ $t('common.close') }}
|
||||
</button>
|
||||
</template>
|
||||
</BaseDialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseDialog from './BaseDialog.vue';
|
||||
import OrdersPanel from './OrdersPanel.vue';
|
||||
|
||||
export default {
|
||||
name: 'MemberOrdersDialog',
|
||||
components: {
|
||||
BaseDialog,
|
||||
OrdersPanel
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
clubId: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
},
|
||||
member: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'close'],
|
||||
computed: {
|
||||
dialogTitle() {
|
||||
if (!this.member) {
|
||||
return this.$t('orders.title');
|
||||
}
|
||||
return this.$t('orders.memberTitle', {
|
||||
name: [this.member.firstName, this.member.lastName].filter(Boolean).join(' ')
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
503
frontend/src/components/OrdersPanel.vue
Normal file
503
frontend/src/components/OrdersPanel.vue
Normal file
@@ -0,0 +1,503 @@
|
||||
<template>
|
||||
<div class="orders-panel">
|
||||
<div class="orders-toolbar" :class="{ 'orders-toolbar-global': globalMode }">
|
||||
<div v-if="globalMode" class="orders-filters">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="search"
|
||||
class="orders-search"
|
||||
:placeholder="$t('orders.searchPlaceholder')"
|
||||
>
|
||||
<select v-model="statusFilter">
|
||||
<option value="">{{ $t('orders.filterAllStatuses') }}</option>
|
||||
<option v-for="status in statusOptions" :key="status.value" :value="status.value">
|
||||
{{ status.label }}
|
||||
</option>
|
||||
</select>
|
||||
<select v-model="clubFilter">
|
||||
<option value="">{{ $t('orders.filterAllClubs') }}</option>
|
||||
<option v-for="club in availableClubs" :key="club.id" :value="String(club.id)">
|
||||
{{ club.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="button" class="btn-secondary" :disabled="loading" @click="loadOrders">
|
||||
{{ $t('common.refresh') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="!globalMode" class="order-create-card">
|
||||
<div class="order-create-grid">
|
||||
<label>
|
||||
<span>{{ $t('orders.item') }}</span>
|
||||
<input v-model.trim="newOrder.item" type="text" :placeholder="$t('orders.itemPlaceholder')">
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ $t('orders.cost') }}</span>
|
||||
<input v-model="newOrder.cost" type="number" min="0" step="0.01">
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ $t('orders.paid') }}</span>
|
||||
<input v-model="newOrder.paidAmount" type="number" min="0" step="0.01">
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ $t('orders.status') }}</span>
|
||||
<select v-model="newOrder.status">
|
||||
<option v-for="status in statusOptions" :key="status.value" :value="status.value">
|
||||
{{ status.label }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="order-create-actions">
|
||||
<button type="button" class="btn-primary" :disabled="creatingOrder || !newOrder.item" @click="createOrder">
|
||||
{{ $t('orders.addOrder') }}
|
||||
</button>
|
||||
<span class="orders-create-hint">{{ $t('orders.dateAutoHint') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="orders-state orders-state-info">{{ $t('orders.loading') }}</div>
|
||||
<div v-else-if="error" class="orders-state orders-state-error">{{ error }}</div>
|
||||
<div v-else-if="filteredOrders.length === 0" class="orders-state orders-state-empty">
|
||||
{{ globalMode ? $t('orders.noOrdersGlobal') : $t('orders.noOrdersMember') }}
|
||||
</div>
|
||||
|
||||
<div v-else class="orders-table-wrap">
|
||||
<table class="orders-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-if="globalMode">{{ $t('orders.club') }}</th>
|
||||
<th v-if="globalMode">{{ $t('orders.member') }}</th>
|
||||
<th>{{ $t('orders.item') }}</th>
|
||||
<th>{{ $t('orders.status') }}</th>
|
||||
<th>{{ $t('orders.orderDate') }}</th>
|
||||
<th>{{ $t('orders.statusDate') }}</th>
|
||||
<th>{{ $t('orders.cost') }}</th>
|
||||
<th>{{ $t('orders.paid') }}</th>
|
||||
<th>{{ $t('orders.open') }}</th>
|
||||
<th>{{ $t('orders.history') }}</th>
|
||||
<th>{{ $t('common.save') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="order in filteredOrders" :key="order.id">
|
||||
<td v-if="globalMode">{{ order.club?.name || '–' }}</td>
|
||||
<td v-if="globalMode">{{ formatMemberName(order.member) }}</td>
|
||||
<td>
|
||||
<input v-model.trim="order.draftItem" type="text" class="orders-inline-input">
|
||||
</td>
|
||||
<td>
|
||||
<select v-model="order.draftStatus">
|
||||
<option v-for="status in statusOptions" :key="status.value" :value="status.value">
|
||||
{{ status.label }}
|
||||
</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>{{ formatDateTime(order.orderDate || order.createdAt) }}</td>
|
||||
<td>{{ formatDateTime(order.statusDate || order.updatedAt) }}</td>
|
||||
<td>
|
||||
<input v-model="order.draftCost" type="number" min="0" step="0.01" class="orders-inline-input orders-inline-input-number">
|
||||
</td>
|
||||
<td>
|
||||
<input v-model="order.draftPaidAmount" type="number" min="0" step="0.01" class="orders-inline-input orders-inline-input-number">
|
||||
</td>
|
||||
<td>{{ formatCurrency(calculateOpenAmount(order)) }}</td>
|
||||
<td>
|
||||
<details class="orders-history-details">
|
||||
<summary>{{ order.historyEntries?.length || 0 }}</summary>
|
||||
<div class="orders-history-list">
|
||||
<div v-for="entry in order.historyEntries || []" :key="entry.id" class="orders-history-entry">
|
||||
<strong>{{ statusLabel(entry.status) }}</strong>
|
||||
<span>{{ formatDateTime(entry.changedAt) }}</span>
|
||||
<span>{{ formatCurrency(entry.cost) }} / {{ formatCurrency(entry.paidAmount) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-primary btn-small"
|
||||
:disabled="savingOrderIds.includes(order.id) || !hasOrderChanges(order)"
|
||||
@click="saveOrder(order)"
|
||||
>
|
||||
{{ savingOrderIds.includes(order.id) ? $t('common.loading') : $t('common.save') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '../apiClient.js';
|
||||
import { getSafeErrorMessage } from '../utils/errorMessages.js';
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
{ value: 'requested', labelKey: 'orders.statusRequested' },
|
||||
{ value: 'ordered', labelKey: 'orders.statusOrdered' },
|
||||
{ value: 'arrived', labelKey: 'orders.statusArrived' },
|
||||
{ value: 'handed_over', labelKey: 'orders.statusHandedOver' }
|
||||
];
|
||||
|
||||
const normalizeAmount = (value) => {
|
||||
const parsed = Number.parseFloat(value ?? 0);
|
||||
if (!Number.isFinite(parsed)) {
|
||||
return 0;
|
||||
}
|
||||
return Math.max(0, Number(parsed.toFixed(2)));
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'OrdersPanel',
|
||||
props: {
|
||||
clubId: {
|
||||
type: [String, Number],
|
||||
default: null
|
||||
},
|
||||
member: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
globalMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
orders: [],
|
||||
loading: false,
|
||||
error: '',
|
||||
creatingOrder: false,
|
||||
savingOrderIds: [],
|
||||
searchQuery: '',
|
||||
statusFilter: '',
|
||||
clubFilter: '',
|
||||
newOrder: {
|
||||
item: '',
|
||||
status: 'requested',
|
||||
cost: '',
|
||||
paidAmount: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
statusOptions() {
|
||||
return STATUS_OPTIONS.map((status) => ({
|
||||
...status,
|
||||
label: this.$t(status.labelKey)
|
||||
}));
|
||||
},
|
||||
availableClubs() {
|
||||
const map = new Map();
|
||||
this.orders.forEach((order) => {
|
||||
if (order.club?.id != null) {
|
||||
map.set(String(order.club.id), { id: String(order.club.id), name: order.club.name });
|
||||
}
|
||||
});
|
||||
return Array.from(map.values()).sort((a, b) => a.name.localeCompare(b.name, 'de-DE'));
|
||||
},
|
||||
filteredOrders() {
|
||||
return this.orders.filter((order) => {
|
||||
if (this.statusFilter && order.status !== this.statusFilter) {
|
||||
return false;
|
||||
}
|
||||
if (this.clubFilter && String(order.clubId) !== String(this.clubFilter)) {
|
||||
return false;
|
||||
}
|
||||
if (!this.searchQuery.trim()) {
|
||||
return true;
|
||||
}
|
||||
const search = this.searchQuery.trim().toLowerCase();
|
||||
const haystack = [
|
||||
order.item,
|
||||
order.club?.name,
|
||||
order.member?.firstName,
|
||||
order.member?.lastName
|
||||
].filter(Boolean).join(' ').toLowerCase();
|
||||
return haystack.includes(search);
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
clubId() {
|
||||
if (!this.globalMode && this.member?.id) {
|
||||
this.loadOrders();
|
||||
}
|
||||
},
|
||||
member: {
|
||||
deep: false,
|
||||
handler() {
|
||||
if (!this.globalMode && this.member?.id) {
|
||||
this.loadOrders();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadOrders();
|
||||
},
|
||||
methods: {
|
||||
hydrateOrder(order) {
|
||||
return {
|
||||
...order,
|
||||
cost: normalizeAmount(order.cost),
|
||||
paidAmount: normalizeAmount(order.paidAmount),
|
||||
openAmount: normalizeAmount(order.openAmount),
|
||||
draftItem: order.item || '',
|
||||
draftStatus: order.status || 'requested',
|
||||
draftCost: String(normalizeAmount(order.cost)),
|
||||
draftPaidAmount: String(normalizeAmount(order.paidAmount))
|
||||
};
|
||||
},
|
||||
async loadOrders() {
|
||||
if (!this.globalMode && (!this.clubId || !this.member?.id)) {
|
||||
this.orders = [];
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
try {
|
||||
const url = this.globalMode
|
||||
? '/member-orders/global'
|
||||
: `/member-orders/${this.clubId}/${this.member.id}`;
|
||||
const response = await apiClient.get(url);
|
||||
const orders = response.data?.orders || [];
|
||||
this.orders = orders.map((order) => this.hydrateOrder(order));
|
||||
} catch (error) {
|
||||
this.error = getSafeErrorMessage(error, this.$t('orders.errorLoading'));
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async createOrder() {
|
||||
if (!this.clubId || !this.member?.id || !this.newOrder.item.trim()) {
|
||||
return;
|
||||
}
|
||||
this.creatingOrder = true;
|
||||
this.error = '';
|
||||
try {
|
||||
const response = await apiClient.post(`/member-orders/${this.clubId}/${this.member.id}`, {
|
||||
item: this.newOrder.item.trim(),
|
||||
status: this.newOrder.status,
|
||||
cost: normalizeAmount(this.newOrder.cost),
|
||||
paidAmount: normalizeAmount(this.newOrder.paidAmount)
|
||||
});
|
||||
if (response.data?.order) {
|
||||
this.orders.unshift(this.hydrateOrder(response.data.order));
|
||||
this.newOrder = {
|
||||
item: '',
|
||||
status: 'requested',
|
||||
cost: '',
|
||||
paidAmount: ''
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
this.error = getSafeErrorMessage(error, this.$t('orders.errorSaving'));
|
||||
} finally {
|
||||
this.creatingOrder = false;
|
||||
}
|
||||
},
|
||||
hasOrderChanges(order) {
|
||||
return order.draftItem !== (order.item || '')
|
||||
|| order.draftStatus !== order.status
|
||||
|| normalizeAmount(order.draftCost) !== normalizeAmount(order.cost)
|
||||
|| normalizeAmount(order.draftPaidAmount) !== normalizeAmount(order.paidAmount);
|
||||
},
|
||||
async saveOrder(order) {
|
||||
this.savingOrderIds.push(order.id);
|
||||
this.error = '';
|
||||
try {
|
||||
const response = await apiClient.patch(`/member-orders/${order.clubId}/${order.memberId}/${order.id}`, {
|
||||
item: order.draftItem,
|
||||
status: order.draftStatus,
|
||||
cost: normalizeAmount(order.draftCost),
|
||||
paidAmount: normalizeAmount(order.draftPaidAmount)
|
||||
});
|
||||
if (response.data?.order) {
|
||||
const updated = this.hydrateOrder(response.data.order);
|
||||
this.orders = this.orders.map((entry) => entry.id === order.id ? updated : entry);
|
||||
}
|
||||
} catch (error) {
|
||||
this.error = getSafeErrorMessage(error, this.$t('orders.errorSaving'));
|
||||
} finally {
|
||||
this.savingOrderIds = this.savingOrderIds.filter((id) => id !== order.id);
|
||||
}
|
||||
},
|
||||
formatCurrency(value) {
|
||||
return new Intl.NumberFormat('de-DE', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(normalizeAmount(value));
|
||||
},
|
||||
formatDateTime(value) {
|
||||
if (!value) {
|
||||
return '–';
|
||||
}
|
||||
return new Intl.DateTimeFormat('de-DE', {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short'
|
||||
}).format(new Date(value));
|
||||
},
|
||||
formatMemberName(member) {
|
||||
if (!member) {
|
||||
return '–';
|
||||
}
|
||||
return [member.firstName, member.lastName].filter(Boolean).join(' ');
|
||||
},
|
||||
calculateOpenAmount(order) {
|
||||
return Math.max(0, normalizeAmount(order.draftCost) - normalizeAmount(order.draftPaidAmount));
|
||||
},
|
||||
statusLabel(status) {
|
||||
return this.$t(STATUS_OPTIONS.find((entry) => entry.value === status)?.labelKey || 'orders.statusRequested');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.orders-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.orders-toolbar,
|
||||
.orders-filters,
|
||||
.order-create-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.orders-filters {
|
||||
justify-content: flex-start;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.orders-search {
|
||||
min-width: 220px;
|
||||
flex: 1 1 240px;
|
||||
}
|
||||
|
||||
.order-create-card,
|
||||
.orders-state {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 14px;
|
||||
padding: 1rem;
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
|
||||
.order-create-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 0.85rem;
|
||||
}
|
||||
|
||||
.order-create-grid label,
|
||||
.orders-table td,
|
||||
.orders-table th {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.order-create-grid label span {
|
||||
display: block;
|
||||
margin-bottom: 0.35rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.orders-create-hint {
|
||||
color: var(--text-light);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.orders-table-wrap {
|
||||
overflow: auto;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 14px;
|
||||
background: var(--surface-color);
|
||||
}
|
||||
|
||||
.orders-table {
|
||||
width: 100%;
|
||||
min-width: 980px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.orders-table th,
|
||||
.orders-table td {
|
||||
padding: 0.75rem;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.orders-inline-input {
|
||||
width: 100%;
|
||||
min-width: 9rem;
|
||||
}
|
||||
|
||||
.orders-inline-input-number {
|
||||
min-width: 6.5rem;
|
||||
}
|
||||
|
||||
.orders-history-details summary {
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.orders-history-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
margin-top: 0.5rem;
|
||||
min-width: 16rem;
|
||||
}
|
||||
|
||||
.orders-history-entry {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
font-size: 0.85rem;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.orders-state-info {
|
||||
color: var(--primary-strong);
|
||||
}
|
||||
|
||||
.orders-state-error {
|
||||
color: #9b2c2c;
|
||||
background: #fff5f5;
|
||||
border-color: rgba(155, 44, 44, 0.2);
|
||||
}
|
||||
|
||||
.orders-state-empty {
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
padding: 0.45rem 0.7rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.orders-search,
|
||||
.orders-filters select,
|
||||
.order-create-grid,
|
||||
.orders-toolbar button,
|
||||
.order-create-actions button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.orders-toolbar-global {
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -28,6 +28,7 @@
|
||||
"time": "Zyt",
|
||||
"new": "Neu",
|
||||
"update": "Aktualisiere",
|
||||
"refresh": "Neu lade",
|
||||
"create": "Erstelle",
|
||||
"remove": "Entferne",
|
||||
"select": "Uswähle",
|
||||
@@ -61,6 +62,7 @@
|
||||
"navigation": {
|
||||
"home": "Startseite",
|
||||
"members": "Mitglider",
|
||||
"orders": "Bestellige",
|
||||
"diary": "Tagebuech",
|
||||
"approvals": "Freigabe",
|
||||
"statistics": "Trainings-Statistik",
|
||||
@@ -623,6 +625,37 @@
|
||||
"copyContactSummary": "Kontaktübersicht kopieren",
|
||||
"copyContactSummarySuccess": "Kontaktübersicht in die Zwischenablage kopiert."
|
||||
},
|
||||
"orders": {
|
||||
"title": "Bestellige",
|
||||
"memberTitle": "Bestellige: {name}",
|
||||
"globalTitle": "Bestellige aller Vereine",
|
||||
"globalSubtitle": "Da chasch alli Bestellige vereinsübergreifend aluege und verwalte.",
|
||||
"loading": "Bestellige werde glade...",
|
||||
"errorLoading": "Bestellige hend nöd chöne glade werde.",
|
||||
"errorSaving": "Bestellig het nöd chöne gspeicheret werde.",
|
||||
"searchPlaceholder": "Nach Verein, Mitgliid oder Artikel sueche",
|
||||
"filterAllStatuses": "Alli Status",
|
||||
"filterAllClubs": "Alli Vereine",
|
||||
"item": "Was",
|
||||
"itemPlaceholder": "z. B. Trikot, Hoodie oder Schlägerhülle",
|
||||
"status": "Status",
|
||||
"statusRequested": "gwünscht",
|
||||
"statusOrdered": "bstellt",
|
||||
"statusArrived": "Artikel achoo",
|
||||
"statusHandedOver": "Artikel usghändigt",
|
||||
"cost": "Chöschte",
|
||||
"paid": "Bezahlt",
|
||||
"open": "No offe",
|
||||
"history": "Verlauf",
|
||||
"orderDate": "Erfasst am",
|
||||
"statusDate": "Letschti Änderig",
|
||||
"addOrder": "Bestellig aalege",
|
||||
"dateAutoHint": "S Datum wird automatisch gsetzt und jedi Änderig mit em Datum protokolliert.",
|
||||
"noOrdersMember": "Für das Mitgliid git's no kei Bestellige.",
|
||||
"noOrdersGlobal": "Aktuell git's kei Bestellige.",
|
||||
"club": "Verein",
|
||||
"member": "Mitgliid"
|
||||
},
|
||||
"diary": {
|
||||
"title": "Trainingstagebuch",
|
||||
"date": "Datum",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"today": "Heute",
|
||||
"new": "Neu",
|
||||
"update": "Aktualisieren",
|
||||
"refresh": "Neu laden",
|
||||
"create": "Erstellen",
|
||||
"remove": "Entfernen",
|
||||
"select": "Auswählen",
|
||||
@@ -57,6 +58,7 @@
|
||||
"navigation": {
|
||||
"home": "Startseite",
|
||||
"members": "Mitglieder",
|
||||
"orders": "Bestellungen",
|
||||
"diary": "Tagebuch",
|
||||
"approvals": "Freigaben",
|
||||
"statistics": "Trainings-Statistik",
|
||||
@@ -294,6 +296,37 @@
|
||||
"copyContactSummary": "Kontaktübersicht kopieren",
|
||||
"copyContactSummarySuccess": "Kontaktübersicht in die Zwischenablage kopiert."
|
||||
},
|
||||
"orders": {
|
||||
"title": "Bestellungen",
|
||||
"memberTitle": "Bestellungen: {name}",
|
||||
"globalTitle": "Bestellungen aller Vereine",
|
||||
"globalSubtitle": "Hier werden alle Bestellungen vereinsübergreifend angezeigt und verwaltet.",
|
||||
"loading": "Lade Bestellungen...",
|
||||
"errorLoading": "Bestellungen konnten nicht geladen werden.",
|
||||
"errorSaving": "Bestellung konnte nicht gespeichert werden.",
|
||||
"searchPlaceholder": "Nach Verein, Mitglied oder Artikel suchen",
|
||||
"filterAllStatuses": "Alle Status",
|
||||
"filterAllClubs": "Alle Vereine",
|
||||
"item": "Was",
|
||||
"itemPlaceholder": "z. B. Trikot, Hoodie oder Schlägerhülle",
|
||||
"status": "Status",
|
||||
"statusRequested": "gewünscht",
|
||||
"statusOrdered": "bestellt",
|
||||
"statusArrived": "Artikel angekommen",
|
||||
"statusHandedOver": "Artikel ausgehändigt",
|
||||
"cost": "Kosten",
|
||||
"paid": "Bezahlt",
|
||||
"open": "Noch offen",
|
||||
"history": "Verlauf",
|
||||
"orderDate": "Erfasst am",
|
||||
"statusDate": "Letzte Änderung",
|
||||
"addOrder": "Bestellung anlegen",
|
||||
"dateAutoHint": "Datum wird automatisch gesetzt und jede Änderung mit Datum protokolliert.",
|
||||
"noOrdersMember": "Für dieses Mitglied gibt es noch keine Bestellungen.",
|
||||
"noOrdersGlobal": "Es gibt aktuell keine Bestellungen.",
|
||||
"club": "Verein",
|
||||
"member": "Mitglied"
|
||||
},
|
||||
"diary": {
|
||||
"title": "Trainingstagebuch",
|
||||
"date": "Datum",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"time": "Zeit",
|
||||
"new": "Neu",
|
||||
"update": "Aktualisieren",
|
||||
"refresh": "Neu laden",
|
||||
"create": "Erstellen",
|
||||
"remove": "Entfernen",
|
||||
"select": "Auswählen",
|
||||
@@ -61,6 +62,7 @@
|
||||
"navigation": {
|
||||
"home": "Startseite",
|
||||
"members": "Mitglieder",
|
||||
"orders": "Bestellungen",
|
||||
"diary": "Tagebuch",
|
||||
"approvals": "Freigaben",
|
||||
"statistics": "Trainings-Statistik",
|
||||
@@ -400,6 +402,37 @@
|
||||
"copyContactSummary": "Kontaktübersicht kopieren",
|
||||
"copyContactSummarySuccess": "Kontaktübersicht in die Zwischenablage kopiert."
|
||||
},
|
||||
"orders": {
|
||||
"title": "Bestellungen",
|
||||
"memberTitle": "Bestellungen: {name}",
|
||||
"globalTitle": "Bestellungen aller Vereine",
|
||||
"globalSubtitle": "Hier werden alle Bestellungen vereinsübergreifend angezeigt und verwaltet.",
|
||||
"loading": "Lade Bestellungen...",
|
||||
"errorLoading": "Bestellungen konnten nicht geladen werden.",
|
||||
"errorSaving": "Bestellung konnte nicht gespeichert werden.",
|
||||
"searchPlaceholder": "Nach Verein, Mitglied oder Artikel suchen",
|
||||
"filterAllStatuses": "Alle Status",
|
||||
"filterAllClubs": "Alle Vereine",
|
||||
"item": "Was",
|
||||
"itemPlaceholder": "z. B. Trikot, Hoodie oder Schlägerhülle",
|
||||
"status": "Status",
|
||||
"statusRequested": "gewünscht",
|
||||
"statusOrdered": "bestellt",
|
||||
"statusArrived": "Artikel angekommen",
|
||||
"statusHandedOver": "Artikel ausgehändigt",
|
||||
"cost": "Kosten",
|
||||
"paid": "Bezahlt",
|
||||
"open": "Noch offen",
|
||||
"history": "Verlauf",
|
||||
"orderDate": "Erfasst am",
|
||||
"statusDate": "Letzte Änderung",
|
||||
"addOrder": "Bestellung anlegen",
|
||||
"dateAutoHint": "Datum wird automatisch gesetzt und jede Änderung mit Datum protokolliert.",
|
||||
"noOrdersMember": "Für dieses Mitglied gibt es noch keine Bestellungen.",
|
||||
"noOrdersGlobal": "Es gibt aktuell keine Bestellungen.",
|
||||
"club": "Verein",
|
||||
"member": "Mitglied"
|
||||
},
|
||||
"diary": {
|
||||
"title": "Trainingstagebuch",
|
||||
"date": "Datum",
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"time": "Time",
|
||||
"new": "New",
|
||||
"update": "Update",
|
||||
"refresh": "Reload",
|
||||
"create": "Create",
|
||||
"remove": "Remove",
|
||||
"select": "Select",
|
||||
@@ -61,6 +62,7 @@
|
||||
"navigation": {
|
||||
"home": "Home",
|
||||
"members": "Members",
|
||||
"orders": "Orders",
|
||||
"diary": "Diary",
|
||||
"approvals": "Approvals",
|
||||
"statistics": "Training Statistics",
|
||||
@@ -623,6 +625,37 @@
|
||||
"copyContactSummary": "Copy contact summary",
|
||||
"copyContactSummarySuccess": "Contact summary copied to clipboard."
|
||||
},
|
||||
"orders": {
|
||||
"title": "Orders",
|
||||
"memberTitle": "Orders: {name}",
|
||||
"globalTitle": "Orders Across All Clubs",
|
||||
"globalSubtitle": "All orders across clubs can be viewed and managed here.",
|
||||
"loading": "Loading orders...",
|
||||
"errorLoading": "Orders could not be loaded.",
|
||||
"errorSaving": "Order could not be saved.",
|
||||
"searchPlaceholder": "Search by club, member or item",
|
||||
"filterAllStatuses": "All statuses",
|
||||
"filterAllClubs": "All clubs",
|
||||
"item": "Item",
|
||||
"itemPlaceholder": "e.g. shirt, hoodie or bat cover",
|
||||
"status": "Status",
|
||||
"statusRequested": "requested",
|
||||
"statusOrdered": "ordered",
|
||||
"statusArrived": "item arrived",
|
||||
"statusHandedOver": "item handed over",
|
||||
"cost": "Cost",
|
||||
"paid": "Paid",
|
||||
"open": "Outstanding",
|
||||
"history": "History",
|
||||
"orderDate": "Created on",
|
||||
"statusDate": "Last change",
|
||||
"addOrder": "Create order",
|
||||
"dateAutoHint": "The date is set automatically and every change is logged with a date.",
|
||||
"noOrdersMember": "There are no orders for this member yet.",
|
||||
"noOrdersGlobal": "There are currently no orders.",
|
||||
"club": "Club",
|
||||
"member": "Member"
|
||||
},
|
||||
"diary": {
|
||||
"title": "Training diary",
|
||||
"date": "Date",
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"time": "Time",
|
||||
"new": "New",
|
||||
"update": "Update",
|
||||
"refresh": "Reload",
|
||||
"create": "Create",
|
||||
"remove": "Remove",
|
||||
"select": "Select",
|
||||
@@ -61,6 +62,7 @@
|
||||
"navigation": {
|
||||
"home": "Home",
|
||||
"members": "Members",
|
||||
"orders": "Orders",
|
||||
"diary": "Diary",
|
||||
"approvals": "Approvals",
|
||||
"statistics": "Training Statistics",
|
||||
@@ -176,6 +178,37 @@
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel"
|
||||
},
|
||||
"orders": {
|
||||
"title": "Orders",
|
||||
"memberTitle": "Orders: {name}",
|
||||
"globalTitle": "Orders Across All Clubs",
|
||||
"globalSubtitle": "All orders across clubs can be viewed and managed here.",
|
||||
"loading": "Loading orders...",
|
||||
"errorLoading": "Orders could not be loaded.",
|
||||
"errorSaving": "Order could not be saved.",
|
||||
"searchPlaceholder": "Search by club, member or item",
|
||||
"filterAllStatuses": "All statuses",
|
||||
"filterAllClubs": "All clubs",
|
||||
"item": "Item",
|
||||
"itemPlaceholder": "e.g. shirt, hoodie or bat cover",
|
||||
"status": "Status",
|
||||
"statusRequested": "requested",
|
||||
"statusOrdered": "ordered",
|
||||
"statusArrived": "item arrived",
|
||||
"statusHandedOver": "item handed over",
|
||||
"cost": "Cost",
|
||||
"paid": "Paid",
|
||||
"open": "Outstanding",
|
||||
"history": "History",
|
||||
"orderDate": "Created on",
|
||||
"statusDate": "Last change",
|
||||
"addOrder": "Create order",
|
||||
"dateAutoHint": "The date is set automatically and every change is logged with a date.",
|
||||
"noOrdersMember": "There are no orders for this member yet.",
|
||||
"noOrdersGlobal": "There are currently no orders.",
|
||||
"club": "Club",
|
||||
"member": "Member"
|
||||
},
|
||||
"diary": {
|
||||
"title": "Training diary",
|
||||
"date": "Date",
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"time": "Time",
|
||||
"new": "New",
|
||||
"update": "Update",
|
||||
"refresh": "Reload",
|
||||
"create": "Create",
|
||||
"remove": "Remove",
|
||||
"select": "Select",
|
||||
@@ -61,6 +62,7 @@
|
||||
"navigation": {
|
||||
"home": "Home",
|
||||
"members": "Members",
|
||||
"orders": "Orders",
|
||||
"diary": "Diary",
|
||||
"approvals": "Approvals",
|
||||
"statistics": "Training Statistics",
|
||||
@@ -623,6 +625,37 @@
|
||||
"copyContactSummary": "Copy contact summary",
|
||||
"copyContactSummarySuccess": "Contact summary copied to clipboard."
|
||||
},
|
||||
"orders": {
|
||||
"title": "Orders",
|
||||
"memberTitle": "Orders: {name}",
|
||||
"globalTitle": "Orders Across All Clubs",
|
||||
"globalSubtitle": "All orders across clubs can be viewed and managed here.",
|
||||
"loading": "Loading orders...",
|
||||
"errorLoading": "Orders could not be loaded.",
|
||||
"errorSaving": "Order could not be saved.",
|
||||
"searchPlaceholder": "Search by club, member, or item",
|
||||
"filterAllStatuses": "All statuses",
|
||||
"filterAllClubs": "All clubs",
|
||||
"item": "Item",
|
||||
"itemPlaceholder": "e.g. jersey, hoodie, or paddle case",
|
||||
"status": "Status",
|
||||
"statusRequested": "requested",
|
||||
"statusOrdered": "ordered",
|
||||
"statusArrived": "item arrived",
|
||||
"statusHandedOver": "item handed over",
|
||||
"cost": "Cost",
|
||||
"paid": "Paid",
|
||||
"open": "Outstanding",
|
||||
"history": "History",
|
||||
"orderDate": "Created on",
|
||||
"statusDate": "Last update",
|
||||
"addOrder": "Create order",
|
||||
"dateAutoHint": "The date is set automatically and every change is logged with a date.",
|
||||
"noOrdersMember": "There are no orders for this member yet.",
|
||||
"noOrdersGlobal": "There are currently no orders.",
|
||||
"club": "Club",
|
||||
"member": "Member"
|
||||
},
|
||||
"diary": {
|
||||
"title": "Training diary",
|
||||
"date": "Date",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"time": "Hora",
|
||||
"new": "Nuevo",
|
||||
"update": "Actualizar",
|
||||
"refresh": "Recargar",
|
||||
"create": "Crear",
|
||||
"remove": "Quitar",
|
||||
"select": "Seleccionar",
|
||||
@@ -61,6 +62,7 @@
|
||||
"navigation": {
|
||||
"home": "Inicio",
|
||||
"members": "Miembros",
|
||||
"orders": "Pedidos",
|
||||
"diary": "Diario",
|
||||
"approvals": "Aprobaciones",
|
||||
"statistics": "Estadísticas de entrenamiento",
|
||||
@@ -595,6 +597,37 @@
|
||||
"copyContactSummary": "Copiar resumen de contactos",
|
||||
"copyContactSummarySuccess": "Resumen de contactos copiado al portapapeles."
|
||||
},
|
||||
"orders": {
|
||||
"title": "Pedidos",
|
||||
"memberTitle": "Pedidos: {name}",
|
||||
"globalTitle": "Pedidos de todos los clubes",
|
||||
"globalSubtitle": "Aquí se pueden ver y gestionar los pedidos de todos los clubes.",
|
||||
"loading": "Cargando pedidos...",
|
||||
"errorLoading": "No se pudieron cargar los pedidos.",
|
||||
"errorSaving": "No se pudo guardar el pedido.",
|
||||
"searchPlaceholder": "Buscar por club, miembro o artículo",
|
||||
"filterAllStatuses": "Todos los estados",
|
||||
"filterAllClubs": "Todos los clubes",
|
||||
"item": "Artículo",
|
||||
"itemPlaceholder": "p. ej. camiseta, sudadera o funda de pala",
|
||||
"status": "Estado",
|
||||
"statusRequested": "deseado",
|
||||
"statusOrdered": "pedido",
|
||||
"statusArrived": "artículo recibido",
|
||||
"statusHandedOver": "artículo entregado",
|
||||
"cost": "Coste",
|
||||
"paid": "Pagado",
|
||||
"open": "Pendiente",
|
||||
"history": "Historial",
|
||||
"orderDate": "Creado el",
|
||||
"statusDate": "Último cambio",
|
||||
"addOrder": "Crear pedido",
|
||||
"dateAutoHint": "La fecha se establece automáticamente y cada cambio se registra con fecha.",
|
||||
"noOrdersMember": "Todavía no hay pedidos para este miembro.",
|
||||
"noOrdersGlobal": "Actualmente no hay pedidos.",
|
||||
"club": "Club",
|
||||
"member": "Miembro"
|
||||
},
|
||||
"diary": {
|
||||
"title": "Diario de entrenamiento",
|
||||
"date": "Fecha",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"time": "Oras",
|
||||
"new": "Bago",
|
||||
"update": "I-update",
|
||||
"refresh": "I-reload",
|
||||
"create": "Lumikha",
|
||||
"remove": "Alisin",
|
||||
"select": "Pumili",
|
||||
@@ -61,6 +62,7 @@
|
||||
"navigation": {
|
||||
"home": "Home",
|
||||
"members": "Mga miyembro",
|
||||
"orders": "Mga order",
|
||||
"diary": "Talaarawan",
|
||||
"approvals": "Mga pag-apruba",
|
||||
"statistics": "Istatistika ng pagsasanay",
|
||||
@@ -595,6 +597,37 @@
|
||||
"copyContactSummary": "Copy contact summary",
|
||||
"copyContactSummarySuccess": "Contact summary copied to clipboard."
|
||||
},
|
||||
"orders": {
|
||||
"title": "Mga order",
|
||||
"memberTitle": "Mga order: {name}",
|
||||
"globalTitle": "Mga order ng lahat ng club",
|
||||
"globalSubtitle": "Dito makikita at mapapamahalaan ang lahat ng order mula sa lahat ng club.",
|
||||
"loading": "Nilo-load ang mga order...",
|
||||
"errorLoading": "Hindi ma-load ang mga order.",
|
||||
"errorSaving": "Hindi ma-save ang order.",
|
||||
"searchPlaceholder": "Maghanap ayon sa club, miyembro, o item",
|
||||
"filterAllStatuses": "Lahat ng status",
|
||||
"filterAllClubs": "Lahat ng club",
|
||||
"item": "Item",
|
||||
"itemPlaceholder": "hal. jersey, hoodie, o lagayan ng raketa",
|
||||
"status": "Status",
|
||||
"statusRequested": "ninanais",
|
||||
"statusOrdered": "na-order na",
|
||||
"statusArrived": "dumating na ang item",
|
||||
"statusHandedOver": "naibigay na ang item",
|
||||
"cost": "Halaga",
|
||||
"paid": "Bayad",
|
||||
"open": "Natitira",
|
||||
"history": "Kasaysayan",
|
||||
"orderDate": "Ginawa noong",
|
||||
"statusDate": "Huling pagbabago",
|
||||
"addOrder": "Gumawa ng order",
|
||||
"dateAutoHint": "Awtomatikong itinatakda ang petsa at bawat pagbabago ay sine-save kasama ang petsa.",
|
||||
"noOrdersMember": "Wala pang order para sa miyembrong ito.",
|
||||
"noOrdersGlobal": "Wala pang mga order sa ngayon.",
|
||||
"club": "Club",
|
||||
"member": "Miyembro"
|
||||
},
|
||||
"diary": {
|
||||
"title": "Talaarawan ng pagsasanay",
|
||||
"date": "Petsa",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"time": "Heure",
|
||||
"new": "Nouveau",
|
||||
"update": "Mettre à jour",
|
||||
"refresh": "Recharger",
|
||||
"create": "Créer",
|
||||
"remove": "Retirer",
|
||||
"select": "Sélectionner",
|
||||
@@ -61,6 +62,7 @@
|
||||
"navigation": {
|
||||
"home": "Accueil",
|
||||
"members": "Membres",
|
||||
"orders": "Commandes",
|
||||
"diary": "Journal",
|
||||
"approvals": "Approbations",
|
||||
"statistics": "Statistiques d'entraînement",
|
||||
@@ -595,6 +597,37 @@
|
||||
"copyContactSummary": "Copier le résumé des contacts",
|
||||
"copyContactSummarySuccess": "Résumé des contacts copié dans le presse-papiers."
|
||||
},
|
||||
"orders": {
|
||||
"title": "Commandes",
|
||||
"memberTitle": "Commandes : {name}",
|
||||
"globalTitle": "Commandes de tous les clubs",
|
||||
"globalSubtitle": "Toutes les commandes de tous les clubs peuvent être consultées et gérées ici.",
|
||||
"loading": "Chargement des commandes...",
|
||||
"errorLoading": "Impossible de charger les commandes.",
|
||||
"errorSaving": "Impossible d'enregistrer la commande.",
|
||||
"searchPlaceholder": "Rechercher un club, un membre ou un article",
|
||||
"filterAllStatuses": "Tous les statuts",
|
||||
"filterAllClubs": "Tous les clubs",
|
||||
"item": "Article",
|
||||
"itemPlaceholder": "p. ex. maillot, hoodie ou housse de raquette",
|
||||
"status": "Statut",
|
||||
"statusRequested": "souhaité",
|
||||
"statusOrdered": "commandé",
|
||||
"statusArrived": "article arrivé",
|
||||
"statusHandedOver": "article remis",
|
||||
"cost": "Coût",
|
||||
"paid": "Payé",
|
||||
"open": "Reste à payer",
|
||||
"history": "Historique",
|
||||
"orderDate": "Créé le",
|
||||
"statusDate": "Dernière modification",
|
||||
"addOrder": "Créer une commande",
|
||||
"dateAutoHint": "La date est définie automatiquement et chaque modification est enregistrée avec une date.",
|
||||
"noOrdersMember": "Aucune commande n'existe encore pour ce membre.",
|
||||
"noOrdersGlobal": "Il n'y a actuellement aucune commande.",
|
||||
"club": "Club",
|
||||
"member": "Membre"
|
||||
},
|
||||
"diary": {
|
||||
"title": "Journal d'entraînement",
|
||||
"date": "Date",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"time": "Ora",
|
||||
"new": "Nuovo",
|
||||
"update": "Aggiorna",
|
||||
"refresh": "Ricarica",
|
||||
"create": "Crea",
|
||||
"remove": "Rimuovi",
|
||||
"select": "Seleziona",
|
||||
@@ -61,6 +62,7 @@
|
||||
"navigation": {
|
||||
"home": "Home",
|
||||
"members": "Membri",
|
||||
"orders": "Ordini",
|
||||
"diary": "Diario",
|
||||
"approvals": "Approvazioni",
|
||||
"statistics": "Statistiche di allenamento",
|
||||
@@ -595,6 +597,37 @@
|
||||
"copyContactSummary": "Copia riepilogo contatti",
|
||||
"copyContactSummarySuccess": "Riepilogo contatti copiato negli appunti."
|
||||
},
|
||||
"orders": {
|
||||
"title": "Ordini",
|
||||
"memberTitle": "Ordini: {name}",
|
||||
"globalTitle": "Ordini di tutti i club",
|
||||
"globalSubtitle": "Qui è possibile visualizzare e gestire tutti gli ordini di tutti i club.",
|
||||
"loading": "Caricamento ordini...",
|
||||
"errorLoading": "Impossibile caricare gli ordini.",
|
||||
"errorSaving": "Impossibile salvare l'ordine.",
|
||||
"searchPlaceholder": "Cerca per club, membro o articolo",
|
||||
"filterAllStatuses": "Tutti gli stati",
|
||||
"filterAllClubs": "Tutti i club",
|
||||
"item": "Articolo",
|
||||
"itemPlaceholder": "ad es. maglia, felpa o custodia per racchetta",
|
||||
"status": "Stato",
|
||||
"statusRequested": "richiesto",
|
||||
"statusOrdered": "ordinato",
|
||||
"statusArrived": "articolo arrivato",
|
||||
"statusHandedOver": "articolo consegnato",
|
||||
"cost": "Costo",
|
||||
"paid": "Pagato",
|
||||
"open": "Da pagare",
|
||||
"history": "Cronologia",
|
||||
"orderDate": "Creato il",
|
||||
"statusDate": "Ultima modifica",
|
||||
"addOrder": "Crea ordine",
|
||||
"dateAutoHint": "La data viene impostata automaticamente e ogni modifica viene registrata con una data.",
|
||||
"noOrdersMember": "Non ci sono ancora ordini per questo membro.",
|
||||
"noOrdersGlobal": "Attualmente non ci sono ordini.",
|
||||
"club": "Club",
|
||||
"member": "Membro"
|
||||
},
|
||||
"diary": {
|
||||
"title": "Diario di allenamento",
|
||||
"date": "Data",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"time": "時刻",
|
||||
"new": "新規",
|
||||
"update": "更新",
|
||||
"refresh": "再読み込み",
|
||||
"create": "作成",
|
||||
"remove": "削除",
|
||||
"select": "選択",
|
||||
@@ -61,6 +62,7 @@
|
||||
"navigation": {
|
||||
"home": "ホーム",
|
||||
"members": "メンバー",
|
||||
"orders": "注文",
|
||||
"diary": "日記",
|
||||
"approvals": "承認",
|
||||
"statistics": "トレーニング統計",
|
||||
@@ -595,6 +597,37 @@
|
||||
"copyContactSummary": "Copy contact summary",
|
||||
"copyContactSummarySuccess": "Contact summary copied to clipboard."
|
||||
},
|
||||
"orders": {
|
||||
"title": "注文",
|
||||
"memberTitle": "注文: {name}",
|
||||
"globalTitle": "全クラブの注文",
|
||||
"globalSubtitle": "ここではクラブ横断で全ての注文を表示・管理できます。",
|
||||
"loading": "注文を読み込み中...",
|
||||
"errorLoading": "注文を読み込めませんでした。",
|
||||
"errorSaving": "注文を保存できませんでした。",
|
||||
"searchPlaceholder": "クラブ、メンバー、または商品で検索",
|
||||
"filterAllStatuses": "すべての状態",
|
||||
"filterAllClubs": "すべてのクラブ",
|
||||
"item": "商品",
|
||||
"itemPlaceholder": "例: シャツ、パーカー、ラケットケース",
|
||||
"status": "状態",
|
||||
"statusRequested": "希望",
|
||||
"statusOrdered": "注文済み",
|
||||
"statusArrived": "商品到着",
|
||||
"statusHandedOver": "商品引き渡し済み",
|
||||
"cost": "費用",
|
||||
"paid": "支払済み",
|
||||
"open": "未払い",
|
||||
"history": "履歴",
|
||||
"orderDate": "登録日",
|
||||
"statusDate": "最終変更",
|
||||
"addOrder": "注文を追加",
|
||||
"dateAutoHint": "日付は自動設定され、すべての変更が日付付きで記録されます。",
|
||||
"noOrdersMember": "このメンバーにはまだ注文がありません。",
|
||||
"noOrdersGlobal": "現在注文はありません。",
|
||||
"club": "クラブ",
|
||||
"member": "メンバー"
|
||||
},
|
||||
"diary": {
|
||||
"title": "練習日誌",
|
||||
"date": "日付",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"time": "Czas",
|
||||
"new": "Nowy",
|
||||
"update": "Aktualizuj",
|
||||
"refresh": "Odśwież",
|
||||
"create": "Utwórz",
|
||||
"remove": "Usuń",
|
||||
"select": "Wybierz",
|
||||
@@ -61,6 +62,7 @@
|
||||
"navigation": {
|
||||
"home": "Strona główna",
|
||||
"members": "Członkowie",
|
||||
"orders": "Zamówienia",
|
||||
"diary": "Dziennik",
|
||||
"approvals": "Zatwierdzenia",
|
||||
"statistics": "Statystyki treningowe",
|
||||
@@ -595,6 +597,37 @@
|
||||
"copyContactSummary": "Kopiuj podsumowanie kontaktów",
|
||||
"copyContactSummarySuccess": "Podsumowanie kontaktów skopiowano do schowka."
|
||||
},
|
||||
"orders": {
|
||||
"title": "Zamówienia",
|
||||
"memberTitle": "Zamówienia: {name}",
|
||||
"globalTitle": "Zamówienia ze wszystkich klubów",
|
||||
"globalSubtitle": "Tutaj można przeglądać i zarządzać zamówieniami ze wszystkich klubów.",
|
||||
"loading": "Ładowanie zamówień...",
|
||||
"errorLoading": "Nie udało się załadować zamówień.",
|
||||
"errorSaving": "Nie udało się zapisać zamówienia.",
|
||||
"searchPlaceholder": "Szukaj po klubie, członku lub artykule",
|
||||
"filterAllStatuses": "Wszystkie statusy",
|
||||
"filterAllClubs": "Wszystkie kluby",
|
||||
"item": "Artykuł",
|
||||
"itemPlaceholder": "np. koszulka, bluza lub pokrowiec na rakietkę",
|
||||
"status": "Status",
|
||||
"statusRequested": "pożądane",
|
||||
"statusOrdered": "zamówione",
|
||||
"statusArrived": "artykuł dotarł",
|
||||
"statusHandedOver": "artykuł wydany",
|
||||
"cost": "Koszt",
|
||||
"paid": "Zapłacono",
|
||||
"open": "Pozostało",
|
||||
"history": "Historia",
|
||||
"orderDate": "Utworzono",
|
||||
"statusDate": "Ostatnia zmiana",
|
||||
"addOrder": "Dodaj zamówienie",
|
||||
"dateAutoHint": "Data jest ustawiana automatycznie, a każda zmiana jest zapisywana z datą.",
|
||||
"noOrdersMember": "Ten członek nie ma jeszcze żadnych zamówień.",
|
||||
"noOrdersGlobal": "Obecnie nie ma żadnych zamówień.",
|
||||
"club": "Klub",
|
||||
"member": "Członek"
|
||||
},
|
||||
"diary": {
|
||||
"title": "Dziennik treningowy",
|
||||
"date": "Data",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"time": "เวลา",
|
||||
"new": "ใหม่",
|
||||
"update": "อัปเดต",
|
||||
"refresh": "โหลดใหม่",
|
||||
"create": "สร้าง",
|
||||
"remove": "เอาออก",
|
||||
"select": "เลือก",
|
||||
@@ -61,6 +62,7 @@
|
||||
"navigation": {
|
||||
"home": "หน้าแรก",
|
||||
"members": "สมาชิก",
|
||||
"orders": "คำสั่งซื้อ",
|
||||
"diary": "ไดอารี่",
|
||||
"approvals": "การอนุมัติ",
|
||||
"statistics": "สถิติการฝึกซ้อม",
|
||||
@@ -595,6 +597,37 @@
|
||||
"copyContactSummary": "Copy contact summary",
|
||||
"copyContactSummarySuccess": "Contact summary copied to clipboard."
|
||||
},
|
||||
"orders": {
|
||||
"title": "คำสั่งซื้อ",
|
||||
"memberTitle": "คำสั่งซื้อ: {name}",
|
||||
"globalTitle": "คำสั่งซื้อของทุกสโมสร",
|
||||
"globalSubtitle": "สามารถดูและจัดการคำสั่งซื้อทั้งหมดข้ามทุกสโมสรได้ที่นี่",
|
||||
"loading": "กำลังโหลดคำสั่งซื้อ...",
|
||||
"errorLoading": "ไม่สามารถโหลดคำสั่งซื้อได้",
|
||||
"errorSaving": "ไม่สามารถบันทึกคำสั่งซื้อได้",
|
||||
"searchPlaceholder": "ค้นหาตามสโมสร สมาชิก หรือสินค้า",
|
||||
"filterAllStatuses": "ทุกสถานะ",
|
||||
"filterAllClubs": "ทุกสโมสร",
|
||||
"item": "สินค้า",
|
||||
"itemPlaceholder": "เช่น เสื้อแข่ง ฮู้ดดี้ หรือซองไม้",
|
||||
"status": "สถานะ",
|
||||
"statusRequested": "ต้องการ",
|
||||
"statusOrdered": "สั่งแล้ว",
|
||||
"statusArrived": "สินค้ามาถึงแล้ว",
|
||||
"statusHandedOver": "ส่งมอบสินค้าแล้ว",
|
||||
"cost": "ค่าใช้จ่าย",
|
||||
"paid": "ชำระแล้ว",
|
||||
"open": "ค้างชำระ",
|
||||
"history": "ประวัติ",
|
||||
"orderDate": "วันที่สร้าง",
|
||||
"statusDate": "การเปลี่ยนแปลงล่าสุด",
|
||||
"addOrder": "สร้างคำสั่งซื้อ",
|
||||
"dateAutoHint": "ระบบจะกำหนดวันที่ให้อัตโนมัติ และทุกการเปลี่ยนแปลงจะถูกบันทึกพร้อมวันที่",
|
||||
"noOrdersMember": "ยังไม่มีคำสั่งซื้อสำหรับสมาชิกคนนี้",
|
||||
"noOrdersGlobal": "ขณะนี้ยังไม่มีคำสั่งซื้อ",
|
||||
"club": "สโมสร",
|
||||
"member": "สมาชิก"
|
||||
},
|
||||
"diary": {
|
||||
"title": "สมุดบันทึกการฝึกซ้อม",
|
||||
"date": "วันที่",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"time": "Oras",
|
||||
"new": "Bago",
|
||||
"update": "I-update",
|
||||
"refresh": "I-reload",
|
||||
"create": "Lumikha",
|
||||
"remove": "Alisin",
|
||||
"select": "Pumili",
|
||||
@@ -61,6 +62,7 @@
|
||||
"navigation": {
|
||||
"home": "Home",
|
||||
"members": "Mga miyembro",
|
||||
"orders": "Mga order",
|
||||
"diary": "Talaarawan",
|
||||
"approvals": "Mga pag-apruba",
|
||||
"statistics": "Mga istatistika ng pagsasanay",
|
||||
@@ -595,6 +597,37 @@
|
||||
"copyContactSummary": "Copy contact summary",
|
||||
"copyContactSummarySuccess": "Contact summary copied to clipboard."
|
||||
},
|
||||
"orders": {
|
||||
"title": "Mga order",
|
||||
"memberTitle": "Mga order: {name}",
|
||||
"globalTitle": "Mga order ng lahat ng club",
|
||||
"globalSubtitle": "Dito makikita at mapapamahalaan ang lahat ng order mula sa iba’t ibang club.",
|
||||
"loading": "Nilo-load ang mga order...",
|
||||
"errorLoading": "Hindi ma-load ang mga order.",
|
||||
"errorSaving": "Hindi ma-save ang order.",
|
||||
"searchPlaceholder": "Maghanap ayon sa club, miyembro, o item",
|
||||
"filterAllStatuses": "Lahat ng status",
|
||||
"filterAllClubs": "Lahat ng club",
|
||||
"item": "Item",
|
||||
"itemPlaceholder": "hal. jersey, hoodie, o lagayan ng raketa",
|
||||
"status": "Status",
|
||||
"statusRequested": "hinihiling",
|
||||
"statusOrdered": "na-order na",
|
||||
"statusArrived": "dumating na ang item",
|
||||
"statusHandedOver": "naibigay na ang item",
|
||||
"cost": "Halaga",
|
||||
"paid": "Nabayaran",
|
||||
"open": "Natitira",
|
||||
"history": "Kasaysayan",
|
||||
"orderDate": "Ginawa noong",
|
||||
"statusDate": "Huling pagbabago",
|
||||
"addOrder": "Gumawa ng order",
|
||||
"dateAutoHint": "Awtomatikong itinatakda ang petsa at bawat pagbabago ay sine-save kasama ang petsa.",
|
||||
"noOrdersMember": "Wala pang order para sa miyembrong ito.",
|
||||
"noOrdersGlobal": "Wala pang mga order sa ngayon.",
|
||||
"club": "Club",
|
||||
"member": "Miyembro"
|
||||
},
|
||||
"diary": {
|
||||
"title": "Talaarawan ng pagsasanay",
|
||||
"date": "Petsa",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"time": "时间",
|
||||
"new": "新建",
|
||||
"update": "更新",
|
||||
"refresh": "重新加载",
|
||||
"create": "创建",
|
||||
"remove": "移除",
|
||||
"select": "选择",
|
||||
@@ -61,6 +62,7 @@
|
||||
"navigation": {
|
||||
"home": "首页",
|
||||
"members": "成员",
|
||||
"orders": "订单",
|
||||
"diary": "日记",
|
||||
"approvals": "审批",
|
||||
"statistics": "训练统计",
|
||||
@@ -595,6 +597,37 @@
|
||||
"copyContactSummary": "Copy contact summary",
|
||||
"copyContactSummarySuccess": "Contact summary copied to clipboard."
|
||||
},
|
||||
"orders": {
|
||||
"title": "订单",
|
||||
"memberTitle": "订单:{name}",
|
||||
"globalTitle": "所有俱乐部的订单",
|
||||
"globalSubtitle": "可在此跨俱乐部查看和管理所有订单。",
|
||||
"loading": "正在加载订单...",
|
||||
"errorLoading": "无法加载订单。",
|
||||
"errorSaving": "无法保存订单。",
|
||||
"searchPlaceholder": "按俱乐部、成员或商品搜索",
|
||||
"filterAllStatuses": "所有状态",
|
||||
"filterAllClubs": "所有俱乐部",
|
||||
"item": "商品",
|
||||
"itemPlaceholder": "例如:球衣、卫衣或球拍套",
|
||||
"status": "状态",
|
||||
"statusRequested": "希望购买",
|
||||
"statusOrdered": "已订购",
|
||||
"statusArrived": "商品已到",
|
||||
"statusHandedOver": "商品已发放",
|
||||
"cost": "费用",
|
||||
"paid": "已支付",
|
||||
"open": "未结清",
|
||||
"history": "历史",
|
||||
"orderDate": "创建时间",
|
||||
"statusDate": "最后变更",
|
||||
"addOrder": "新建订单",
|
||||
"dateAutoHint": "日期会自动设置,并且每次变更都会连同日期一起记录。",
|
||||
"noOrdersMember": "该成员目前还没有订单。",
|
||||
"noOrdersGlobal": "当前没有订单。",
|
||||
"club": "俱乐部",
|
||||
"member": "成员"
|
||||
},
|
||||
"diary": {
|
||||
"title": "训练日记",
|
||||
"date": "日期",
|
||||
|
||||
@@ -27,6 +27,7 @@ const LogsView = () => import('./views/LogsView.vue');
|
||||
const ClickTtView = () => import('./views/ClickTtView.vue');
|
||||
const MemberTransferSettingsView = () => import('./views/MemberTransferSettingsView.vue');
|
||||
const PersonalSettings = () => import('./views/PersonalSettings.vue');
|
||||
const OrdersView = () => import('./views/OrdersView.vue');
|
||||
const Impressum = () => import('./views/Impressum.vue');
|
||||
const Datenschutz = () => import('./views/Datenschutz.vue');
|
||||
|
||||
@@ -56,6 +57,7 @@ const routes = [
|
||||
{ path: '/clicktt', name: 'clicktt', component: ClickTtView },
|
||||
{ path: '/member-transfer-settings', name: 'member-transfer-settings', component: MemberTransferSettingsView },
|
||||
{ path: '/personal-settings', name: 'personal-settings', component: PersonalSettings },
|
||||
{ path: '/orders', name: 'orders', component: OrdersView },
|
||||
{ path: '/impressum', name: 'impressum', component: Impressum, meta: { public: true } },
|
||||
{ path: '/datenschutz', name: 'datenschutz', component: Datenschutz, meta: { public: true } },
|
||||
];
|
||||
|
||||
@@ -566,6 +566,7 @@
|
||||
@open-notes="openNotesModal"
|
||||
@show-pic="showPic"
|
||||
@mark-form="markFormHandedOver"
|
||||
@open-orders="openOrdersDialog"
|
||||
@open-tags="openTagInfos"
|
||||
@open-quick-add="openQuickAddDialog"
|
||||
@open-gallery="openGalleryDialog"
|
||||
@@ -671,6 +672,14 @@
|
||||
:should-show-member="shouldShowGalleryMember"
|
||||
@member-click="handleGalleryMemberClick"
|
||||
/>
|
||||
|
||||
<MemberOrdersDialog
|
||||
v-if="showMemberOrdersDialog && selectedMemberForOrders"
|
||||
v-model="showMemberOrdersDialog"
|
||||
:member="selectedMemberForOrders"
|
||||
:club-id="currentClub"
|
||||
@close="closeOrdersDialog"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Info Dialog -->
|
||||
@@ -712,6 +721,7 @@ import MemberActivityStatsDialog from '../components/MemberActivityStatsDialog.v
|
||||
import AccidentFormDialog from '../components/AccidentFormDialog.vue';
|
||||
import QuickAddMemberDialog from '../components/QuickAddMemberDialog.vue';
|
||||
import MemberGalleryDialog from '../components/MemberGalleryDialog.vue';
|
||||
import MemberOrdersDialog from '../components/MemberOrdersDialog.vue';
|
||||
import DiaryParticipantsPanel from '../components/DiaryParticipantsPanel.vue';
|
||||
import DiaryActivitiesPanel from '../components/DiaryActivitiesPanel.vue';
|
||||
import DiaryOverviewPanels from '../components/diary/DiaryOverviewPanels.vue';
|
||||
@@ -776,6 +786,7 @@ export default {
|
||||
AccidentFormDialog,
|
||||
QuickAddMemberDialog,
|
||||
MemberGalleryDialog,
|
||||
MemberOrdersDialog,
|
||||
DiaryParticipantsPanel,
|
||||
DiaryActivitiesPanel,
|
||||
DiaryOverviewPanels
|
||||
@@ -818,8 +829,10 @@ export default {
|
||||
notes: [],
|
||||
newNoteContent: '',
|
||||
noteMember: null,
|
||||
selectedMemberForOrders: null,
|
||||
selectedMember: null,
|
||||
showNotesModal: false,
|
||||
showMemberOrdersDialog: false,
|
||||
selectedActivityTags: [],
|
||||
selectedMemberTags: [],
|
||||
selectedMemberDayTags: [],
|
||||
@@ -1797,6 +1810,14 @@ export default {
|
||||
this.loadMemberNotesAndTags(this.date.id, member.id);
|
||||
this.showNotesModal = true;
|
||||
},
|
||||
openOrdersDialog(member) {
|
||||
this.selectedMemberForOrders = member;
|
||||
this.showMemberOrdersDialog = true;
|
||||
},
|
||||
closeOrdersDialog() {
|
||||
this.showMemberOrdersDialog = false;
|
||||
this.selectedMemberForOrders = null;
|
||||
},
|
||||
|
||||
async loadMemberNotesAndTags(diaryDateId, memberId) {
|
||||
this.doMemberTagUpdates = false;
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
</div>
|
||||
<div class="member-preview-actions">
|
||||
<button type="button" class="btn-primary" @click="editMember(selectedMemberPreview)">{{ $t('members.editMember') }}</button>
|
||||
<button type="button" @click="openOrdersDialog(selectedMemberPreview)">{{ $t('orders.title') }}</button>
|
||||
<button type="button" @click="openImageModal(selectedMemberPreview)">{{ $t('members.memberImages') }}</button>
|
||||
<button type="button" @click="openNotesModal(selectedMemberPreview)">{{ $t('members.notes') }}</button>
|
||||
<button type="button" @click="openActivitiesModal(selectedMemberPreview)">{{ $t('members.exercises') }}</button>
|
||||
@@ -440,6 +441,7 @@
|
||||
{{ clickTtPendingMemberIds.includes(member.id) ? '⏳' : '🏓' }}
|
||||
</button>
|
||||
<button type="button" class="member-icon-button member-icon-button-edit" :title="$t('members.editMember')" @click.stop="editMember(member)">🪶</button>
|
||||
<button type="button" class="member-icon-button" :title="$t('orders.title')" @click.stop="openOrdersDialog(member)">📦</button>
|
||||
<button type="button" class="member-icon-button" :title="$t('members.notes')" @click.stop="openNotesModal(member)">📝</button>
|
||||
<button type="button" class="member-icon-button" :title="$t('members.exercises')" @click.stop="openActivitiesModal(member)">🏃</button>
|
||||
<button
|
||||
@@ -536,6 +538,14 @@
|
||||
:club-id="currentClub"
|
||||
@close="closeTtrHistoryDialog"
|
||||
/>
|
||||
|
||||
<MemberOrdersDialog
|
||||
v-if="showMemberOrdersDialog && selectedMemberForOrders"
|
||||
v-model="showMemberOrdersDialog"
|
||||
:member="selectedMemberForOrders"
|
||||
:club-id="currentClub"
|
||||
@close="closeOrdersDialog"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -553,6 +563,7 @@ import MemberNotesDialog from '../components/MemberNotesDialog.vue';
|
||||
import MemberActivitiesDialog from '../components/MemberActivitiesDialog.vue';
|
||||
import MemberTransferDialog from '../components/MemberTransferDialog.vue';
|
||||
import MemberTtrHistoryDialog from '../components/MemberTtrHistoryDialog.vue';
|
||||
import MemberOrdersDialog from '../components/MemberOrdersDialog.vue';
|
||||
import MembersOverviewSection from '../components/members/MembersOverviewSection.vue';
|
||||
import { debounce } from '../utils/debounce.js';
|
||||
import { buildInfoConfig, buildConfirmConfig, safeErrorMessage, sanitizeText } from '../utils/dialogUtils.js';
|
||||
@@ -567,6 +578,7 @@ export default {
|
||||
MemberActivitiesDialog,
|
||||
MemberTransferDialog,
|
||||
MemberTtrHistoryDialog,
|
||||
MemberOrdersDialog,
|
||||
MembersOverviewSection
|
||||
},
|
||||
computed: {
|
||||
@@ -903,8 +915,10 @@ export default {
|
||||
showMemberInfo: false,
|
||||
showActivitiesModal: false,
|
||||
showMemberTtrHistoryDialog: false,
|
||||
showMemberOrdersDialog: false,
|
||||
selectedMemberForActivities: null,
|
||||
selectedMemberForTtrHistory: null,
|
||||
selectedMemberForOrders: null,
|
||||
memberTrainingGroups: [],
|
||||
trainingGroups: [],
|
||||
selectedGroupToAdd: '',
|
||||
@@ -2279,6 +2293,17 @@ export default {
|
||||
this.showMemberTtrHistoryDialog = false;
|
||||
this.selectedMemberForTtrHistory = null;
|
||||
},
|
||||
openOrdersDialog(member) {
|
||||
if (!member) {
|
||||
return;
|
||||
}
|
||||
this.selectedMemberForOrders = member;
|
||||
this.showMemberOrdersDialog = true;
|
||||
},
|
||||
closeOrdersDialog() {
|
||||
this.showMemberOrdersDialog = false;
|
||||
this.selectedMemberForOrders = null;
|
||||
},
|
||||
async updateRatingsFromMyTischtennis() {
|
||||
this.isUpdatingRatings = true;
|
||||
try {
|
||||
|
||||
35
frontend/src/views/OrdersView.vue
Normal file
35
frontend/src/views/OrdersView.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div class="orders-view">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2>{{ $t('orders.globalTitle') }}</h2>
|
||||
<p class="orders-view-subtitle">{{ $t('orders.globalSubtitle') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<OrdersPanel global-mode />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import OrdersPanel from '../components/OrdersPanel.vue';
|
||||
|
||||
export default {
|
||||
name: 'OrdersView',
|
||||
components: {
|
||||
OrdersPanel
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.orders-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.orders-view-subtitle {
|
||||
margin: 0.35rem 0 0;
|
||||
color: var(--text-light);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user