refactor: use vuetify form controls
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { computed, reactive } from 'vue';
|
||||
import { mdiAt, mdiClose, mdiImagePlusOutline, mdiLockOutline, mdiSend } from '@mdi/js';
|
||||
import AppAvatar from '@/components/AppAvatar.vue';
|
||||
import { useUserProfileStore } from '@/features/user-profile/stores/userProfileStore.js';
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
const emit = defineEmits(['submit-comment', 'cancel-reply']);
|
||||
const userProfileStore = useUserProfileStore();
|
||||
const mediaFileInput = ref(null);
|
||||
|
||||
const form = reactive({
|
||||
body: '',
|
||||
@@ -73,25 +72,15 @@
|
||||
form.isInternal = false;
|
||||
form.mediaFile = null;
|
||||
form.showMentionPicker = false;
|
||||
if (mediaFileInput.value) {
|
||||
mediaFileInput.value.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function openMediaPicker() {
|
||||
mediaFileInput.value?.click();
|
||||
function selectMediaFile(file) {
|
||||
form.mediaFile = Array.isArray(file) ? file[0] ?? null : file;
|
||||
form.showMentionPicker = false;
|
||||
}
|
||||
|
||||
function selectMediaFile(event) {
|
||||
form.mediaFile = event.target.files?.[0] ?? null;
|
||||
}
|
||||
|
||||
function clearMediaFile() {
|
||||
form.mediaFile = null;
|
||||
if (mediaFileInput.value) {
|
||||
mediaFileInput.value.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMentionPicker() {
|
||||
@@ -143,11 +132,15 @@
|
||||
size="md"
|
||||
/>
|
||||
|
||||
<textarea
|
||||
<v-textarea
|
||||
v-model="form.body"
|
||||
class="comment-textarea"
|
||||
:placeholder="replyTarget ? 'Write a reply...' : 'Write a comment...'"
|
||||
></textarea>
|
||||
variant="outlined"
|
||||
hide-details
|
||||
auto-grow
|
||||
rows="3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -193,32 +186,27 @@
|
||||
|
||||
<div class="comment-composer-toolbar">
|
||||
<div class="comment-tool-actions">
|
||||
<label
|
||||
<div
|
||||
class="icon-tool-button internal-toggle"
|
||||
:class="{ active: form.isInternal }"
|
||||
title="Internal comment"
|
||||
>
|
||||
<input
|
||||
<v-checkbox-btn
|
||||
v-model="form.isInternal"
|
||||
type="checkbox"
|
||||
density="compact"
|
||||
/>
|
||||
<v-icon :icon="mdiLockOutline" />
|
||||
</label>
|
||||
<button
|
||||
class="icon-tool-button"
|
||||
type="button"
|
||||
</div>
|
||||
<v-file-input
|
||||
v-model="form.mediaFile"
|
||||
class="media-file-control"
|
||||
title="Upload media from computer"
|
||||
:class="{ active: form.mediaFile }"
|
||||
@click="openMediaPicker"
|
||||
>
|
||||
<v-icon :icon="mdiImagePlusOutline" />
|
||||
</button>
|
||||
<input
|
||||
ref="mediaFileInput"
|
||||
class="sr-only"
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/jpg"
|
||||
@change="selectMediaFile"
|
||||
:prepend-icon="mdiImagePlusOutline"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
@update:model-value="selectMediaFile"
|
||||
/>
|
||||
<button
|
||||
class="icon-tool-button"
|
||||
|
||||
@@ -787,40 +787,31 @@
|
||||
</div>
|
||||
|
||||
<div class="form-grid">
|
||||
<label class="field">
|
||||
<span>Title</span>
|
||||
<input
|
||||
v-model="form.title"
|
||||
type="text"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="field">
|
||||
<span>Campaign</span>
|
||||
<select v-model="form.campaignId">
|
||||
<option
|
||||
disabled
|
||||
value=""
|
||||
>
|
||||
Select a campaign
|
||||
</option>
|
||||
<option
|
||||
v-for="campaign in availableCampaigns"
|
||||
:key="campaign.id"
|
||||
:value="campaign.id"
|
||||
>
|
||||
{{ campaign.name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="field">
|
||||
<span>Due date</span>
|
||||
<input
|
||||
v-model="form.dueDate"
|
||||
type="date"
|
||||
<v-text-field
|
||||
v-model="form.title"
|
||||
label="Title"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
</label>
|
||||
|
||||
<v-select
|
||||
v-model="form.campaignId"
|
||||
:items="availableCampaigns"
|
||||
label="Campaign"
|
||||
item-title="name"
|
||||
item-value="id"
|
||||
placeholder="Select a campaign"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
|
||||
<v-text-field
|
||||
v-model="form.dueDate"
|
||||
label="Due date"
|
||||
type="date"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
|
||||
<div class="date-context field-wide">
|
||||
<div class="date-context-days">
|
||||
@@ -864,19 +855,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="field field-wide">
|
||||
<span>Change summary</span>
|
||||
<input
|
||||
v-model="form.changeSummary"
|
||||
type="text"
|
||||
placeholder="What changed in this revision?"
|
||||
/>
|
||||
</label>
|
||||
<v-text-field
|
||||
v-model="form.changeSummary"
|
||||
class="field-wide"
|
||||
label="Change summary"
|
||||
placeholder="What changed in this revision?"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
|
||||
<label class="field field-wide">
|
||||
<span>Shared brief / base caption</span>
|
||||
<textarea v-model="form.body"></textarea>
|
||||
</label>
|
||||
<v-textarea
|
||||
v-model="form.body"
|
||||
class="field-wide"
|
||||
label="Shared brief / base caption"
|
||||
rows="4"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
|
||||
<label class="field field-wide">
|
||||
<span>Shared hashtags</span>
|
||||
@@ -899,11 +894,13 @@
|
||||
{{ tag.startsWith('#') ? tag : `#${tag}` }}
|
||||
</v-chip>
|
||||
</div>
|
||||
<input
|
||||
<v-text-field
|
||||
v-model="form.hashtags"
|
||||
type="text"
|
||||
class="hashtags-inline-input"
|
||||
placeholder="#launch #campaign #brand"
|
||||
density="compact"
|
||||
variant="plain"
|
||||
hide-details
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
@@ -961,63 +958,61 @@
|
||||
</div>
|
||||
|
||||
<div class="form-grid compact-grid">
|
||||
<label class="field">
|
||||
<span>Network</span>
|
||||
<input
|
||||
v-model="placement.network"
|
||||
type="text"
|
||||
placeholder="Instagram, YouTube..."
|
||||
/>
|
||||
</label>
|
||||
<v-text-field
|
||||
v-model="placement.network"
|
||||
label="Network"
|
||||
placeholder="Instagram, YouTube..."
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
|
||||
<label class="field">
|
||||
<span>Channel</span>
|
||||
<select
|
||||
v-model="placement.channelId"
|
||||
@change="syncPlacementChannel(placement, placement.channelId)"
|
||||
>
|
||||
<option value="">Select a configured channel</option>
|
||||
<option
|
||||
v-for="channel in availableChannels"
|
||||
:key="channel.id"
|
||||
:value="channel.id"
|
||||
>
|
||||
{{ channel.name }}{{ channel.network ? ` · ${channel.network}` : '' }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<v-select
|
||||
v-model="placement.channelId"
|
||||
:items="availableChannels.map(channel => ({
|
||||
title: `${channel.name}${channel.network ? ` · ${channel.network}` : ''}`,
|
||||
value: channel.id,
|
||||
}))"
|
||||
label="Channel"
|
||||
placeholder="Select a configured channel"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
clearable
|
||||
@update:model-value="syncPlacementChannel(placement, $event)"
|
||||
/>
|
||||
|
||||
<label class="field">
|
||||
<span>Channel name</span>
|
||||
<input
|
||||
v-model="placement.channelName"
|
||||
type="text"
|
||||
placeholder="IG Feed, YouTube Main..."
|
||||
/>
|
||||
</label>
|
||||
<v-text-field
|
||||
v-model="placement.channelName"
|
||||
label="Channel name"
|
||||
placeholder="IG Feed, YouTube Main..."
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
|
||||
<label class="field">
|
||||
<span>Variant label</span>
|
||||
<input
|
||||
v-model="placement.variantLabel"
|
||||
type="text"
|
||||
placeholder="Reel version, Shorts version..."
|
||||
/>
|
||||
</label>
|
||||
<v-text-field
|
||||
v-model="placement.variantLabel"
|
||||
label="Variant label"
|
||||
placeholder="Reel version, Shorts version..."
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
|
||||
<label class="field field-wide">
|
||||
<span>Channel-specific caption</span>
|
||||
<textarea v-model="placement.message"></textarea>
|
||||
</label>
|
||||
<v-textarea
|
||||
v-model="placement.message"
|
||||
class="field-wide"
|
||||
label="Channel-specific caption"
|
||||
rows="3"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
|
||||
<label class="field field-wide">
|
||||
<span>Channel-specific hashtags</span>
|
||||
<input
|
||||
v-model="placement.hashtags"
|
||||
type="text"
|
||||
placeholder="#product #launch"
|
||||
/>
|
||||
</label>
|
||||
<v-text-field
|
||||
v-model="placement.hashtags"
|
||||
class="field-wide"
|
||||
label="Channel-specific hashtags"
|
||||
placeholder="#product #launch"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="media-section">
|
||||
@@ -1042,32 +1037,30 @@
|
||||
class="media-card"
|
||||
>
|
||||
<div class="form-grid compact-grid">
|
||||
<label class="field">
|
||||
<span>Media type</span>
|
||||
<select v-model="media.mediaType">
|
||||
<option value="Image">Image</option>
|
||||
<option value="Video">Video</option>
|
||||
<option value="Document">Document</option>
|
||||
</select>
|
||||
</label>
|
||||
<v-select
|
||||
v-model="media.mediaType"
|
||||
:items="['Image', 'Video', 'Document']"
|
||||
label="Media type"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
|
||||
<label class="field">
|
||||
<span>Label</span>
|
||||
<input
|
||||
v-model="media.label"
|
||||
type="text"
|
||||
placeholder="Cover image, YouTube video..."
|
||||
/>
|
||||
</label>
|
||||
<v-text-field
|
||||
v-model="media.label"
|
||||
label="Label"
|
||||
placeholder="Cover image, YouTube video..."
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
|
||||
<label class="field field-wide">
|
||||
<span>Media URL / reference</span>
|
||||
<input
|
||||
v-model="media.url"
|
||||
type="text"
|
||||
placeholder="Google Drive link or asset URL"
|
||||
/>
|
||||
</label>
|
||||
<v-text-field
|
||||
v-model="media.url"
|
||||
class="field-wide"
|
||||
label="Media URL / reference"
|
||||
placeholder="Google Drive link or asset URL"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -1170,46 +1163,43 @@
|
||||
|
||||
<template v-else-if="activeProductionTab === 'assets'">
|
||||
<div class="panel-stack asset-form">
|
||||
<label class="field">
|
||||
<span>Type</span>
|
||||
<select v-model="assetForm.assetType">
|
||||
<option value="Image">Image</option>
|
||||
<option value="Video">Video</option>
|
||||
<option value="Document">Document</option>
|
||||
<option value="Other">Other</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Name</span>
|
||||
<input
|
||||
v-model="assetForm.displayName"
|
||||
type="text"
|
||||
placeholder="Final reel, cover image..."
|
||||
/>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Google Drive link</span>
|
||||
<input
|
||||
v-model="assetForm.googleDriveLink"
|
||||
type="url"
|
||||
placeholder="https://drive.google.com/..."
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>File id</span>
|
||||
<input
|
||||
v-model="assetForm.googleDriveFileId"
|
||||
type="text"
|
||||
placeholder="Optional if link includes it"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Preview URL</span>
|
||||
<input
|
||||
v-model="assetForm.previewUrl"
|
||||
type="url"
|
||||
/>
|
||||
</label>
|
||||
<v-select
|
||||
v-model="assetForm.assetType"
|
||||
:items="['Image', 'Video', 'Document', 'Other']"
|
||||
label="Type"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="assetForm.displayName"
|
||||
label="Name"
|
||||
placeholder="Final reel, cover image..."
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="assetForm.googleDriveLink"
|
||||
class="field-wide"
|
||||
label="Google Drive link"
|
||||
type="url"
|
||||
placeholder="https://drive.google.com/..."
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="assetForm.googleDriveFileId"
|
||||
label="File id"
|
||||
placeholder="Optional if link includes it"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="assetForm.previewUrl"
|
||||
label="Preview URL"
|
||||
type="url"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
<button
|
||||
class="primary-button field-wide"
|
||||
:disabled="detailStore.actions.asset"
|
||||
@@ -1255,22 +1245,23 @@
|
||||
</div>
|
||||
|
||||
<div class="panel-stack compact-form">
|
||||
<label class="field field-wide">
|
||||
<span>New revision reference</span>
|
||||
<input
|
||||
v-model="assetRevisionForm(asset.id).sourceReference"
|
||||
type="url"
|
||||
placeholder="Updated Drive link or production reference"
|
||||
/>
|
||||
</label>
|
||||
<label class="field field-wide">
|
||||
<span>Notes</span>
|
||||
<input
|
||||
v-model="assetRevisionForm(asset.id).notes"
|
||||
type="text"
|
||||
placeholder="What changed?"
|
||||
/>
|
||||
</label>
|
||||
<v-text-field
|
||||
v-model="assetRevisionForm(asset.id).sourceReference"
|
||||
class="field-wide"
|
||||
label="New revision reference"
|
||||
type="url"
|
||||
placeholder="Updated Drive link or production reference"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="assetRevisionForm(asset.id).notes"
|
||||
class="field-wide"
|
||||
label="Notes"
|
||||
placeholder="What changed?"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
<button
|
||||
class="secondary-button"
|
||||
:disabled="detailStore.actions.assetRevision"
|
||||
|
||||
@@ -1302,42 +1302,48 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="scope-row">
|
||||
<label
|
||||
<v-radio-group
|
||||
v-model="customCalendarForm.scope"
|
||||
class="scope-row"
|
||||
inline
|
||||
hide-details
|
||||
>
|
||||
<v-radio
|
||||
v-for="option in addScopeOptions"
|
||||
:key="option.value"
|
||||
class="scope-option"
|
||||
>
|
||||
<input
|
||||
v-model="customCalendarForm.scope"
|
||||
type="radio"
|
||||
:value="option.value"
|
||||
>
|
||||
<span>{{ option.label }}</span>
|
||||
</label>
|
||||
</div>
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</v-radio-group>
|
||||
|
||||
<div
|
||||
v-if="activeAddMode === 'catalog'"
|
||||
class="catalog-panel"
|
||||
>
|
||||
<div class="catalog-search">
|
||||
<input
|
||||
<v-text-field
|
||||
v-model="catalogFilters.search"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
type="search"
|
||||
:placeholder="t('contentItems.calendar.searchCatalog')"
|
||||
>
|
||||
<input
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="catalogFilters.country"
|
||||
type="text"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
maxlength="2"
|
||||
:placeholder="t('contentItems.calendar.country')"
|
||||
>
|
||||
<input
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="catalogFilters.category"
|
||||
type="text"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
:placeholder="t('contentItems.calendar.category')"
|
||||
>
|
||||
/>
|
||||
<button
|
||||
class="text-button"
|
||||
type="button"
|
||||
@@ -1372,31 +1378,41 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form
|
||||
<v-form
|
||||
v-else
|
||||
class="custom-calendar-form"
|
||||
@submit.prevent="addCustomSource"
|
||||
>
|
||||
<input
|
||||
<v-text-field
|
||||
v-model="customCalendarForm.title"
|
||||
type="text"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
:placeholder="t('contentItems.calendar.calendarName')"
|
||||
>
|
||||
<input
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="customCalendarForm.sourceUrl"
|
||||
type="url"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
:placeholder="t('contentItems.calendar.icsUrl')"
|
||||
>
|
||||
/>
|
||||
<div class="custom-form-row">
|
||||
<input
|
||||
<v-text-field
|
||||
v-model="customCalendarForm.color"
|
||||
type="color"
|
||||
>
|
||||
<input
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="customCalendarForm.category"
|
||||
type="text"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
:placeholder="t('contentItems.calendar.category')"
|
||||
>
|
||||
/>
|
||||
<button
|
||||
class="text-button"
|
||||
type="submit"
|
||||
@@ -1405,7 +1421,7 @@
|
||||
<span>{{ t('contentItems.calendar.addCalendar') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</v-form>
|
||||
|
||||
<p
|
||||
v-if="addCalendarError || calendarStore.error"
|
||||
|
||||
Reference in New Issue
Block a user