link edit refactor

This commit is contained in:
nquidox 2025-10-07 20:39:49 +03:00
parent 5593d59279
commit 3a068f57dc
2 changed files with 142 additions and 72 deletions

View file

@ -6,6 +6,7 @@ import PeriodSelector from '@/components/PeriodSelector.vue'
import ChartBlock from '@/components/ChartBlock.vue' import ChartBlock from '@/components/ChartBlock.vue'
import { useChartsApi } from '@/api/charts.js' import { useChartsApi } from '@/api/charts.js'
import EditLink from '@/views/DetailsView/EditLink.vue' import EditLink from '@/views/DetailsView/EditLink.vue'
import CopyToClipboard from '@/components/CopyToClipboard.vue'
const { getMerchDetails, deleteMerch } = useMerchApi() const { getMerchDetails, deleteMerch } = useMerchApi()
const { getDistinctPrices } = useChartsApi() const { getDistinctPrices } = useChartsApi()
@ -17,6 +18,11 @@ const props = defineProps({
}, },
}) })
const editing = ref({
surugaya: false,
mandarake: false,
})
const merchDetails = ref(null) const merchDetails = ref(null)
const loading = ref(true) const loading = ref(true)
const error = ref(null) const error = ref(null)
@ -76,11 +82,15 @@ const fetchPrices = async (days = 7) => {
} }
} }
function handleLinkUpdate(origin, newLink) {
merchDetails.value[`origin_${origin}`].link = newLink
editing.value[origin] = false
}
function handleSelectDays(days) { function handleSelectDays(days) {
fetchPrices(days) fetchPrices(days)
} }
onMounted(() => { onMounted(() => {
fetchMerch() fetchMerch()
fetchPrices(7) fetchPrices(7)
@ -102,20 +112,71 @@ onMounted(() => {
<ChartBlock :charts-data="prices" /> <ChartBlock :charts-data="prices" />
</div> </div>
<!-- Surugaya -->
<CopyToClipboard :text="merchDetails.origin_surugaya?.link">
<n-divider title-placement="left">Surugaya</n-divider> <n-divider title-placement="left">Surugaya</n-divider>
</CopyToClipboard>
<div v-if="!editing.surugaya">
<template v-if="merchDetails.origin_surugaya?.link">
<a
:href="merchDetails.origin_surugaya.link"
target="_blank"
rel="noopener"
class="default-color"
>
{{ merchDetails.origin_surugaya.link }}
</a>
<n-button type="primary" style="margin-left: 8px" @click="editing.surugaya = true">
Edit link
</n-button>
</template>
<template v-else>
<span class="default-color underline link-like-text" @click="editing.surugaya = true">Add link</span>
</template>
</div>
<EditLink <EditLink
v-else
:merch-uuid="merch_uuid" :merch-uuid="merch_uuid"
origin="surugaya" origin="surugaya"
:name="merchDetails.name" :name="merchDetails.name"
v-model="merchDetails.origin_surugaya.link" /> :model-value="merchDetails.origin_surugaya?.link || ''"
@update:model-value="handleLinkUpdate('surugaya', $event)"
@cancel-edit="editing.surugaya = false"
/>
<!-- Mandarake -->
<CopyToClipboard :text="merchDetails.origin_mandarake?.link">
<n-divider title-placement="left">Mandarake</n-divider> <n-divider title-placement="left">Mandarake</n-divider>
</CopyToClipboard>
<div v-if="!editing.mandarake">
<template v-if="merchDetails.origin_mandarake?.link">
<a
:href="merchDetails.origin_mandarake.link"
target="_blank"
rel="noopener"
class="default-color"
>
{{ merchDetails.origin_mandarake.link }}
</a>
<n-button type="primary" style="margin-left: 8px" @click="editing.mandarake = true">
Edit link
</n-button>
</template>
<template v-else>
<span class="default-color underline link-like-text" @click="editing.mandarake = true">Add link</span>
</template>
</div>
<EditLink <EditLink
v-else
:merch-uuid="merch_uuid" :merch-uuid="merch_uuid"
origin="mandarake" origin="mandarake"
:name="merchDetails.name" :name="merchDetails.name"
v-model="merchDetails.origin_mandarake.link" /> :model-value="merchDetails.origin_mandarake?.link || ''"
@update:model-value="handleLinkUpdate('mandarake', $event)"
@cancel-edit="editing.mandarake = false"
/>
</n-card> </n-card>
<div v-else>Not found</div> <div v-else>Not found</div>

View file

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, nextTick, watch, computed } from 'vue' import { ref, nextTick } from 'vue'
import { useMerchApi } from '@/api/merch.js' import { useMerchApi } from '@/api/merch.js'
const { updateMerch } = useMerchApi() const { updateMerch } = useMerchApi()
@ -7,27 +7,19 @@ const { updateMerch } = useMerchApi()
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: String, type: String,
required: true required: true,
},
type: {
type: String,
default: 'text'
}, },
placeholder: { placeholder: {
type: String, type: String,
default: '' default: 'Enter link here...',
},
emptyText: {
type: String,
default: 'Add link'
}, },
merchUuid: { merchUuid: {
type: String, type: String,
required: true required: true,
}, },
name: { name: {
type: String, type: String,
required: true required: true,
}, },
origin: { origin: {
type: String, type: String,
@ -35,32 +27,31 @@ const props = defineProps({
}, },
}) })
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue', 'cancel-edit'])
const isEditing = ref(false)
const tempValue = ref('') const tempValue = ref('')
const inputRef = ref(null) const inputRef = ref(null)
const loading = ref(false) const loading = ref(false)
const displayValue = computed(() => { tempValue.value = props.modelValue
const val = props.modelValue.trim()
return val === '' || val == null ? props.emptyText : props.modelValue
})
const startEditing = () => { nextTick(() => {
tempValue.value = props.modelValue
isEditing.value = true
nextTick(() => {
inputRef.value?.focus?.() inputRef.value?.focus?.()
}) })
}
const save = async () => { const save = async () => {
const newValue = tempValue.value.trim() const newValue = tempValue.value.trim()
if (newValue !== props.modelValue) {
emit('update:modelValue', newValue) if (newValue === '') {
emit('cancel-edit')
return
} }
isEditing.value = false
if (newValue === props.modelValue.trim()) {
emit('cancel-edit')
return
}
loading.value = true loading.value = true
try { try {
@ -68,7 +59,7 @@ const save = async () => {
merch_uuid: props.merchUuid, merch_uuid: props.merchUuid,
name: props.name, name: props.name,
origin: props.origin, origin: props.origin,
link: newValue link: newValue,
}) })
emit('update:modelValue', newValue) emit('update:modelValue', newValue)
@ -76,63 +67,81 @@ const save = async () => {
console.error('Update link error:', error) console.error('Update link error:', error)
} finally { } finally {
loading.value = false loading.value = false
isEditing.value = false
} }
} }
const showModal = ref(false)
const clearLink = async () => {
showModal.value = true
}
const confirmClearLink = async () => {
loading.value = true
try {
await updateMerch({
merch_uuid: props.merchUuid,
name: props.name,
origin: props.origin,
link: '',
})
emit('update:modelValue', '')
} catch (error) {
console.error('Update link error:', error)
} finally {
loading.value = false
}
showModal.value = false
cancel()
}
const cancel = () => { const cancel = () => {
isEditing.value = false emit('cancel-edit')
} }
watch(() => props.modelValue, (newVal) => {
if (isEditing.value) {
tempValue.value = newVal
}
})
</script> </script>
<template> <template>
<div class="editing-area">
<span v-if="!isEditing" @click="startEditing" class="editable-text">
{{ displayValue }}
</span>
<div v-else class="editing-area">
<n-input <n-input
v-model:value="tempValue" v-model:value="tempValue"
type="text" type="textarea"
size="small" size="large"
ref="inputRef" ref="inputRef"
@keyup.enter="save" @keyup.enter="save"
@keyup.esc="cancel" @keyup.esc="cancel"
:placeholder="placeholder || 'Enter link here...'" :placeholder="placeholder"
:loading="loading"
/> />
<n-button size="small" type="primary" @click="save"></n-button> <n-button size="small" type="primary" :loading="loading" @click="save">Save</n-button>
<n-button size="small" @click="cancel"></n-button> <n-button size="small" @click="cancel">Cancel</n-button>
</div> </div>
<div class="center-button-container">
<n-button type="error" class="center-button" @click="clearLink">Clear link</n-button>
</div>
<n-modal v-model:show="showModal" preset="dialog" title="Confirmation">
<template #default>
<p>Confirm clear link</p>
</template>
<template #action>
<n-button @click="confirmClearLink" type="error">Clear</n-button>
<n-button @click="showModal = false">Cancel</n-button>
</template>
</n-modal>
</template> </template>
<style scoped> <style scoped>
.editable-text {
cursor: pointer;
text-decoration: underline;
color: #18a058;
padding: 2px 4px;
border-radius: 4px;
transition: background 0.2s;
min-height: 1.2em;
display: inline-block;
min-width: 50px;
}
.editable-text:hover {
background-color: #f0f9ff;
}
.editing-area { .editing-area {
display: inline-flex; display: flex;
gap: 6px; gap: 6px;
align-items: center; align-items: center;
vertical-align: middle;
width: 100%; width: 100%;
flex-wrap: wrap;
}
.editing-area :deep(.n-input) {
flex: 1;
min-width: 200px;
} }
</style> </style>