Update maintenance Component for Equipment
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
package gsmaster
|
||||
|
||||
import (
|
||||
"bamort/database"
|
||||
"bamort/models"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// EquipmentWithCategories represents equipment with enhanced information
|
||||
type EquipmentWithCategories struct {
|
||||
models.Equipment
|
||||
}
|
||||
|
||||
// GetEquipmentWithCategories retrieves equipment with all its information
|
||||
func GetEquipmentWithCategories(equipmentID uint) (*EquipmentWithCategories, error) {
|
||||
var equipment models.Equipment
|
||||
if err := database.DB.First(&equipment, equipmentID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &EquipmentWithCategories{
|
||||
Equipment: equipment,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetAllEquipmentWithCategories retrieves all equipment
|
||||
func GetAllEquipmentWithCategories() ([]EquipmentWithCategories, error) {
|
||||
var equipments []models.Equipment
|
||||
if err := database.DB.Find(&equipments).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]EquipmentWithCategories, len(equipments))
|
||||
for i, equipment := range equipments {
|
||||
equipmentWithCats, err := GetEquipmentWithCategories(equipment.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[i] = *equipmentWithCats
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// EquipmentUpdateRequest represents the request to update equipment
|
||||
type EquipmentUpdateRequest struct {
|
||||
models.Equipment
|
||||
}
|
||||
|
||||
// UpdateEquipmentWithCategories updates equipment
|
||||
func UpdateEquipmentWithCategories(equipmentID uint, req EquipmentUpdateRequest) error {
|
||||
// Start transaction
|
||||
return database.DB.Transaction(func(tx *gorm.DB) error {
|
||||
// Update equipment info
|
||||
if err := tx.Model(&models.Equipment{}).Where("id = ?", equipmentID).Updates(req.Equipment).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ===== Handler Functions =====
|
||||
|
||||
// GetEnhancedMDEquipment returns equipment with enhanced information
|
||||
func GetEnhancedMDEquipment(c *gin.Context) {
|
||||
equipments, err := GetAllEquipmentWithCategories()
|
||||
if err != nil {
|
||||
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve equipment: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Also get learning sources for the dropdowns
|
||||
var sources []models.Source
|
||||
database.DB.Where("is_active = ?", true).Find(&sources)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"equipment": equipments,
|
||||
"sources": sources,
|
||||
})
|
||||
}
|
||||
|
||||
// GetEnhancedMDEquipmentItem returns a single equipment with enhanced information
|
||||
func GetEnhancedMDEquipmentItem(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||
if err != nil {
|
||||
respondWithError(c, http.StatusBadRequest, "Invalid ID")
|
||||
return
|
||||
}
|
||||
|
||||
equipment, err := GetEquipmentWithCategories(uint(id))
|
||||
if err != nil {
|
||||
respondWithError(c, http.StatusNotFound, "Equipment not found")
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, equipment)
|
||||
}
|
||||
|
||||
// UpdateEnhancedMDEquipmentItem updates equipment
|
||||
func UpdateEnhancedMDEquipmentItem(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||
if err != nil {
|
||||
respondWithError(c, http.StatusBadRequest, "Invalid ID")
|
||||
return
|
||||
}
|
||||
|
||||
var req EquipmentUpdateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
respondWithError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure the ID matches
|
||||
req.Equipment.ID = uint(id)
|
||||
|
||||
if err := UpdateEquipmentWithCategories(uint(id), req); err != nil {
|
||||
respondWithError(c, http.StatusInternalServerError, "Failed to update equipment: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Return updated equipment
|
||||
equipment, err := GetEquipmentWithCategories(uint(id))
|
||||
if err != nil {
|
||||
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve updated equipment")
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, equipment)
|
||||
}
|
||||
@@ -36,8 +36,11 @@ func RegisterRoutes(r *gin.RouterGroup) {
|
||||
maintGrp.DELETE("/spells/:id", DeleteMDSpell)
|
||||
|
||||
maintGrp.GET("/equipment", GetMDEquipments)
|
||||
maintGrp.GET("/equipment-enhanced", GetEnhancedMDEquipment) // New enhanced endpoint
|
||||
maintGrp.GET("/equipment/:id", GetMDEquipment)
|
||||
maintGrp.GET("/equipment-enhanced/:id", GetEnhancedMDEquipmentItem) // New enhanced endpoint
|
||||
maintGrp.PUT("/equipment/:id", UpdateMDEquipment)
|
||||
maintGrp.PUT("/equipment-enhanced/:id", UpdateEnhancedMDEquipmentItem) // New enhanced endpoint
|
||||
maintGrp.POST("/equipment", AddEquipment)
|
||||
maintGrp.DELETE("/equipment/:id", DeleteMDEquipment)
|
||||
|
||||
|
||||
@@ -13,13 +13,35 @@
|
||||
|
||||
<div class="cd-view">
|
||||
<div class="cd-list">
|
||||
<!-- Filter Row -->
|
||||
<div class="filter-row">
|
||||
<div class="filter-item">
|
||||
<label>{{ $t('equipment.personal_item') }}:</label>
|
||||
<select v-model="filterPersonalItem">
|
||||
<option value="">{{ $t('all') || 'All' }}</option>
|
||||
<option value="true">{{ $t('yes') || 'Yes' }}</option>
|
||||
<option value="false">{{ $t('no') || 'No' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="filter-item">
|
||||
<label>{{ $t('equipment.quelle') }}:</label>
|
||||
<select v-model="filterQuelle">
|
||||
<option value="">{{ $t('all') || 'All' }}</option>
|
||||
<option v-for="quelle in availableQuellen" :key="quelle" :value="quelle">{{ quelle }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<button @click="clearFilters" class="btn-clear-filters">{{ $t('clearFilters') || 'Clear Filters' }}</button>
|
||||
</div>
|
||||
|
||||
<div class="tables-container">
|
||||
<table class="cd-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cd-table-header">{{ $t('equipment.id') }}</th>
|
||||
<!-- <th class="cd-table-header">{{ $t('equipment.category') }}<button @click="sortBy('category')">-{{ sortField === 'category' ? (sortAsc ? '↑' : '↓') : '' }}</button></th> -->
|
||||
<th class="cd-table-header">{{ $t('equipment.name') }} <button @click="sortBy('name')">-{{ sortField === 'name' ? (sortAsc ? '↑' : '↓') : '' }}</button></th>
|
||||
<th class="cd-table-header">
|
||||
{{ $t('equipment.name') }}
|
||||
<button @click="sortBy('name')">{{ sortField === 'name' ? (sortAsc ? '↑' : '↓') : '-' }}</button>
|
||||
</th>
|
||||
<th class="cd-table-header">{{ $t('equipment.gewicht') }}</th>
|
||||
<th class="cd-table-header">{{ $t('equipment.wert') }}</th>
|
||||
<th class="cd-table-header">{{ $t('equipment.description') }}</th>
|
||||
@@ -38,32 +60,70 @@
|
||||
<td>{{ dtaItem.gewicht || '-' }}</td>
|
||||
<td>{{ dtaItem.wert || '-' }}</td>
|
||||
<td>{{ dtaItem.beschreibung || '-' }}</td>
|
||||
<td>{{ dtaItem.quelle || '-' }}</td>
|
||||
<td>{{ dtaItem.personal_item || '0' }}</td>
|
||||
<td>{{ formatQuelle(dtaItem) }}</td>
|
||||
<td><input type="checkbox" :checked="dtaItem.personal_item" disabled /></td>
|
||||
<td>{{ dtaItem.system || 'midgard' }}</td>
|
||||
<td>
|
||||
<button @click="startEdit(index)">Edit</button>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Edit Mode -->
|
||||
<tr v-else>
|
||||
<td><input v-model="editedItem.id" style="width:20px;"/></td>
|
||||
<!-- <td><select v-model="editedItem.category" style="width:80px;">
|
||||
<option v-for="category in mdata['equipmentcategories']"
|
||||
:key="category"
|
||||
:value="category">
|
||||
{{ category }}
|
||||
</option>
|
||||
</select></td> -->
|
||||
<td><input v-model="editedItem.name"/></td>
|
||||
<td><input v-model.number="editedItem.gewicht" type="number" style="width:40px;"/></td>
|
||||
<td><input v-model="editedItem.wert" /></td>
|
||||
<td><input v-model="editedItem.beschreibung" /></td>
|
||||
<td><input v-model="editedItem.quelle" style="width:80px;"/></td>
|
||||
<td><input type="checkbox" :checked="true" v-model="editedItem.personal_item" style="width:50px;"/></td>
|
||||
<td><input v-model="editedItem.system" style="width:80px;"/></td>
|
||||
<td>
|
||||
<button @click="saveEdit(index)">Save</button>
|
||||
<button @click="cancelEdit">Cancel</button>
|
||||
<td><input v-model="editedItem.id" style="width:20px;" disabled /></td>
|
||||
<td colspan="8">
|
||||
<!-- Expanded edit form -->
|
||||
<div class="edit-form">
|
||||
<div class="edit-row">
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('equipment.name') }}:</label>
|
||||
<input v-model="editedItem.name" />
|
||||
</div>
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('equipment.gewicht') }}:</label>
|
||||
<input v-model.number="editedItem.gewicht" type="number" style="width:80px;" />
|
||||
</div>
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('equipment.wert') }}:</label>
|
||||
<input v-model="editedItem.wert" style="width:100px;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="edit-row">
|
||||
<div class="edit-field full-width">
|
||||
<label>{{ $t('equipment.description') }}:</label>
|
||||
<input v-model="editedItem.beschreibung" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="edit-row">
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('equipment.quelle') }}:</label>
|
||||
<select v-model="editedItem.sourceCode" style="width:100px;">
|
||||
<option value="">-</option>
|
||||
<option v-for="source in availableSources" :key="source.code" :value="source.code">
|
||||
{{ source.code }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('equipment.page') || 'Page' }}:</label>
|
||||
<input v-model.number="editedItem.page_number" type="number" style="width:60px;" />
|
||||
</div>
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('equipment.personal_item') }}:</label>
|
||||
<input type="checkbox" v-model="editedItem.personal_item" />
|
||||
</div>
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('equipment.system') }}:</label>
|
||||
<input v-model="editedItem.system" style="width:100px;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="edit-actions">
|
||||
<button @click="saveEdit(index)" class="btn-save">Save</button>
|
||||
<button @click="cancelEdit" class="btn-cancel">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
@@ -138,27 +198,65 @@ export default {
|
||||
sortField: 'name',
|
||||
sortAsc: true,
|
||||
editingIndex: -1,
|
||||
editedItem: null
|
||||
editedItem: null,
|
||||
filterPersonalItem: '',
|
||||
filterQuelle: '',
|
||||
enhancedEquipment: [],
|
||||
availableSources: []
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadEnhancedEquipment()
|
||||
},
|
||||
computed: {
|
||||
availableQuellen() {
|
||||
const quellen = new Set()
|
||||
this.enhancedEquipment.forEach(equipment => {
|
||||
if (equipment.source_id && this.availableSources.length > 0) {
|
||||
const source = this.availableSources.find(s => s.id === equipment.source_id)
|
||||
if (source) {
|
||||
quellen.add(source.code)
|
||||
}
|
||||
}
|
||||
})
|
||||
return Array.from(quellen).sort()
|
||||
},
|
||||
filteredAndSortedEquipments() {
|
||||
if (!this.mdata?.equipment) return [];
|
||||
let filtered = [...this.enhancedEquipment]
|
||||
|
||||
return [...this.mdata.equipment]
|
||||
.filter(equipment => {
|
||||
const searchLower = this.searchTerm.toLowerCase();
|
||||
return !this.searchTerm ||
|
||||
equipment.name?.toLowerCase().includes(searchLower)
|
||||
//|| equipment.category?.toLowerCase().includes(searchLower);
|
||||
// Apply search filter
|
||||
if (this.searchTerm) {
|
||||
const searchLower = this.searchTerm.toLowerCase()
|
||||
filtered = filtered.filter(equipment =>
|
||||
equipment.name?.toLowerCase().includes(searchLower)
|
||||
)
|
||||
}
|
||||
|
||||
// Apply personal_item filter
|
||||
if (this.filterPersonalItem !== '') {
|
||||
const personalItemValue = this.filterPersonalItem === 'true'
|
||||
filtered = filtered.filter(equipment => equipment.personal_item === personalItemValue)
|
||||
}
|
||||
|
||||
// Apply Quelle filter (only by source code, ignoring page number)
|
||||
if (this.filterQuelle) {
|
||||
filtered = filtered.filter(equipment => {
|
||||
if (equipment.source_id && this.availableSources.length > 0) {
|
||||
const source = this.availableSources.find(s => s.id === equipment.source_id)
|
||||
return source && source.code === this.filterQuelle
|
||||
}
|
||||
return false
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const aValue = (a[this.sortField] || '').toLowerCase();
|
||||
const bValue = (b[this.sortField] || '').toLowerCase();
|
||||
return this.sortAsc
|
||||
? aValue.localeCompare(bValue)
|
||||
: bValue.localeCompare(aValue);
|
||||
});
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
filtered.sort((a, b) => {
|
||||
const aValue = (a[this.sortField] || '').toString().toLowerCase()
|
||||
const bValue = (b[this.sortField] || '').toString().toLowerCase()
|
||||
return this.sortAsc ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue)
|
||||
})
|
||||
|
||||
return filtered
|
||||
},
|
||||
sortedEquipments() {
|
||||
return [...this.mdata.equipment].sort((a, b) => {
|
||||
@@ -171,15 +269,51 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
startEdit(index) {
|
||||
this.editingIndex = index;
|
||||
this.editedItem = { ...this.filteredAndSortedEquipments[index] };
|
||||
async loadEnhancedEquipment() {
|
||||
try {
|
||||
const response = await API.get('/api/maintenance/equipment-enhanced')
|
||||
this.enhancedEquipment = response.data.equipment || []
|
||||
this.availableSources = response.data.sources || []
|
||||
} catch (error) {
|
||||
console.error('Failed to load enhanced equipment:', error)
|
||||
}
|
||||
},
|
||||
saveEdit(index) {
|
||||
//this.$emit('update-equipment', { index, equipment: this.editedItem });
|
||||
this.handleEquipmentUpdate( { index, equipment: this.editedItem });
|
||||
this.editingIndex = -1;
|
||||
this.editedItem = null;
|
||||
startEdit(index) {
|
||||
const equipment = this.filteredAndSortedEquipments[index]
|
||||
this.editedItem = {
|
||||
...equipment,
|
||||
sourceCode: this.getSourceCode(equipment.source_id)
|
||||
}
|
||||
this.editingIndex = index
|
||||
},
|
||||
async saveEdit(index) {
|
||||
try {
|
||||
// Find source ID from code
|
||||
const source = this.availableSources.find(s => s.code === this.editedItem.sourceCode)
|
||||
|
||||
const updateData = {
|
||||
...this.editedItem,
|
||||
source_id: source ? source.id : null,
|
||||
page_number: this.editedItem.page_number || 0
|
||||
}
|
||||
|
||||
const response = await API.put(
|
||||
`/api/maintenance/equipment-enhanced/${this.editedItem.id}`,
|
||||
updateData
|
||||
)
|
||||
|
||||
// Update the equipment in the list using splice for proper reactivity
|
||||
const equipmentIndex = this.enhancedEquipment.findIndex(e => e.id === this.editedItem.id)
|
||||
if (equipmentIndex !== -1) {
|
||||
this.enhancedEquipment.splice(equipmentIndex, 1, response.data)
|
||||
}
|
||||
|
||||
this.editingIndex = -1
|
||||
this.editedItem = null
|
||||
} catch (error) {
|
||||
console.error('Failed to save equipment:', error)
|
||||
alert('Failed to save equipment: ' + (error.response?.data?.error || error.message))
|
||||
}
|
||||
},
|
||||
cancelEdit() {
|
||||
this.editingIndex = -1;
|
||||
@@ -193,6 +327,31 @@ export default {
|
||||
this.sortAsc = true;
|
||||
}
|
||||
},
|
||||
formatQuelle(equipment) {
|
||||
if (equipment.source_id && this.availableSources.length > 0) {
|
||||
const source = this.availableSources.find(s => s.id === equipment.source_id)
|
||||
if (source) {
|
||||
if (equipment.page_number) {
|
||||
return `${source.code}:${equipment.page_number}`
|
||||
} else {
|
||||
// No page number - show code and quelle if available
|
||||
const quelle = equipment.quelle ? ` (${equipment.quelle})` : ''
|
||||
return `${source.code}${quelle}`
|
||||
}
|
||||
}
|
||||
}
|
||||
return equipment.quelle || '-'
|
||||
},
|
||||
getSourceCode(sourceId) {
|
||||
if (!sourceId || !this.availableSources.length) return ''
|
||||
const source = this.availableSources.find(s => s.id === sourceId)
|
||||
return source ? source.code : ''
|
||||
},
|
||||
clearFilters() {
|
||||
this.searchTerm = ''
|
||||
this.filterPersonalItem = ''
|
||||
this.filterQuelle = ''
|
||||
},
|
||||
async handleEquipmentUpdate({ index, equipment }) {
|
||||
try {
|
||||
const response = await API.put(
|
||||
|
||||
Reference in New Issue
Block a user