refactor: use vuetify form controls

This commit is contained in:
2026-05-07 19:38:51 -04:00
parent 6ac05e1a10
commit 4aaa1a7f90
21 changed files with 724 additions and 774 deletions

View File

@@ -0,0 +1,26 @@
# Task: Replace native form controls with Vuetify controls
## Goal
Move interactive form fields from native `input`, `select`, and `textarea` elements to Vuetify form components so form theming flows through `createVuetify`.
## Scope
- Replace native text, email, URL, search, date, and number inputs with `v-text-field`.
- Replace native selects with `v-select`.
- Replace native textareas with `v-textarea`.
- Replace native checkboxes/radios with Vuetify selection controls where practical.
- Preserve file inputs where Vuetify would reduce custom upload behavior.
- Keep custom navigation and row action buttons out of this pass unless they are part of a form.
## Validation
```bash
cd frontend
npm run build
```
## Done
- [x] Native form controls under `frontend/src/**/*.vue` were replaced with Vuetify form components.
- [x] Frontend build passes.

View File

@@ -44,7 +44,6 @@
const emit = defineEmits(['update:modelValue', 'save']); const emit = defineEmits(['update:modelValue', 'save']);
const fileInput = ref(null);
const cropper = ref(null); const cropper = ref(null);
const imageUrl = ref(null); const imageUrl = ref(null);
const remoteUrl = ref(''); const remoteUrl = ref('');
@@ -67,17 +66,11 @@
imageUrl.value = props.initialUrl || null; imageUrl.value = props.initialUrl || null;
remoteUrl.value = props.initialUrl || ''; remoteUrl.value = props.initialUrl || '';
error.value = null; error.value = null;
if (fileInput.value) {
fileInput.value.value = '';
}
} }
function chooseImage() { function onFileSelected(value) {
fileInput.value?.click(); const file = Array.isArray(value) ? value[0] : value;
}
function onFileSelected(event) {
const [file] = event.target.files ?? [];
if (!file) { if (!file) {
return; return;
} }
@@ -165,28 +158,25 @@
</div> </div>
<div class="cropper-actions"> <div class="cropper-actions">
<input <v-file-input
ref="fileInput" :label="uploadLabel"
type="file"
accept="image/*" accept="image/*"
class="hidden-input"
@change="onFileSelected"
/>
<button
class="action-button"
:disabled="isSaving" :disabled="isSaving"
@click="chooseImage" density="compact"
> variant="outlined"
{{ uploadLabel }} hide-details
</button> @update:model-value="onFileSelected"
/>
<div class="url-controls"> <div class="url-controls">
<input <v-text-field
v-model="remoteUrl" v-model="remoteUrl"
type="url" type="url"
class="url-input" class="url-input"
:placeholder="sourceLabel" :placeholder="sourceLabel"
:disabled="isSaving" :disabled="isSaving"
density="compact"
variant="outlined"
hide-details
/> />
<button <button
class="action-button secondary" class="action-button secondary"

View File

@@ -10,36 +10,25 @@
</p> </p>
<div class="card"> <div class="card">
<form @submit.prevent="handleForgotPassword"> <v-form @submit.prevent="handleForgotPassword">
<div class="card-content"> <div class="card-content">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="form-field"> <v-text-field
<label
class="form-label"
for="email"
>
{{ t('email') }}
</label>
<input
id="email"
v-model="email" v-model="email"
class="form-input" :label="t('email')"
required required
type="email" type="email"
variant="outlined"
/> />
</div>
<button <v-btn
:disabled="isLoading" :loading="isLoading"
class="primary w-full" block
color="primary"
type="submit" type="submit"
> >
<span
v-if="isLoading"
class="loading-spinner mr-2"
></span>
{{ t('resetPassword') }} {{ t('resetPassword') }}
</button> </v-btn>
<div class="text-center mt-4"> <div class="text-center mt-4">
<router-link <router-link
@@ -51,7 +40,7 @@
</div> </div>
</div> </div>
</div> </div>
</form> </v-form>
</div> </div>
<!-- Success message --> <!-- Success message -->

View File

@@ -5,68 +5,34 @@
{{ t('title') }} {{ t('title') }}
</h1> </h1>
<form <v-form
class="card" class="card"
@submit.prevent="handleResetPassword" @submit.prevent="handleResetPassword"
> >
<div class="card-content"> <div class="card-content">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="form-field"> <div>
<label <v-text-field
class="form-label"
for="password"
>
{{ t('newPassword') }}
</label>
<div class="relative">
<input
id="password"
v-model="password" v-model="password"
:append-inner-icon="showPassword ? mdiEyeOff : mdiEye"
:label="t('newPassword')"
:type="showPassword ? 'text' : 'password'" :type="showPassword ? 'text' : 'password'"
class="form-input"
required required
variant="outlined"
@click:append-inner="showPassword = !showPassword"
/> />
<button
class="password-toggle"
type="button"
@click="showPassword = !showPassword"
>
<v-icon
:icon="showPassword ? mdiEyeOff : mdiEye"
size="small"
/>
</button>
</div>
<p class="mt-1 text-sm text-gray-500">{{ t('passwordRequirements') }}</p> <p class="mt-1 text-sm text-gray-500">{{ t('passwordRequirements') }}</p>
</div> </div>
<div class="form-field"> <v-text-field
<label
class="form-label"
for="confirmPassword"
>
{{ t('confirmPassword') }}
</label>
<div class="relative">
<input
id="confirmPassword"
v-model="confirmPassword" v-model="confirmPassword"
:append-inner-icon="showConfirmPassword ? mdiEyeOff : mdiEye"
:label="t('confirmPassword')"
:type="showConfirmPassword ? 'text' : 'password'" :type="showConfirmPassword ? 'text' : 'password'"
class="form-input"
required required
variant="outlined"
@click:append-inner="showConfirmPassword = !showConfirmPassword"
/> />
<button
class="password-toggle"
type="button"
@click="showConfirmPassword = !showConfirmPassword"
>
<v-icon
:icon="showConfirmPassword ? mdiEyeOff : mdiEye"
size="small"
/>
</button>
</div>
</div>
<div <div
v-if="errorMessage" v-if="errorMessage"
@@ -75,20 +41,17 @@
{{ errorMessage }} {{ errorMessage }}
</div> </div>
<button <v-btn
:disabled="isLoading" :loading="isLoading"
class="primary w-full" block
color="primary"
type="submit" type="submit"
> >
<span
v-if="isLoading"
class="loading-spinner mr-2"
></span>
{{ t('resetPassword') }} {{ t('resetPassword') }}
</button> </v-btn>
</div> </div>
</div> </div>
</form> </v-form>
<!-- Success message --> <!-- Success message -->
<div <div

View File

@@ -129,48 +129,52 @@
</div> </div>
<div class="form-grid"> <div class="form-grid">
<label class="field"> <v-text-field
<span>{{ t('campaigns.fields.startDate') }}</span>
<input
v-model="form.startDate" v-model="form.startDate"
type="date" :label="t('campaigns.fields.startDate')"
:disabled="campaignsStore.isCreating" :disabled="campaignsStore.isCreating"
type="date"
variant="outlined"
hide-details
/> />
</label>
<label class="field"> <v-text-field
<span>{{ t('campaigns.fields.endDate') }}</span>
<input
v-model="form.endDate" v-model="form.endDate"
:label="t('campaigns.fields.endDate')"
:disabled="campaignsStore.isCreating"
type="date" type="date"
:disabled="campaignsStore.isCreating" variant="outlined"
hide-details
/> />
</label>
<label class="field field-wide"> <v-text-field
<span>{{ t('campaigns.fields.name') }}</span>
<input
v-model="form.name" v-model="form.name"
type="text" class="field-wide"
:label="t('campaigns.fields.name')"
:disabled="campaignsStore.isCreating" :disabled="campaignsStore.isCreating"
variant="outlined"
hide-details
/> />
</label>
<label class="field field-wide"> <v-textarea
<span>{{ t('campaigns.fields.description') }}</span>
<textarea
v-model="form.description" v-model="form.description"
class="field-wide"
:label="t('campaigns.fields.description')"
:disabled="campaignsStore.isCreating" :disabled="campaignsStore.isCreating"
></textarea> rows="3"
</label> variant="outlined"
hide-details
/>
<label class="field field-wide"> <v-textarea
<span>{{ t('campaigns.fields.notes') }}</span>
<textarea
v-model="form.notes" v-model="form.notes"
class="field-wide"
:label="t('campaigns.fields.notes')"
:disabled="campaignsStore.isCreating" :disabled="campaignsStore.isCreating"
></textarea> rows="3"
</label> variant="outlined"
hide-details
/>
</div> </div>
<div class="panel-actions"> <div class="panel-actions">

View File

@@ -164,13 +164,12 @@
</div> </div>
<div class="form-grid"> <div class="form-grid">
<label class="field"> <v-text-field
<span>{{ t('channels.fields.name') }}</span>
<input
v-model="form.name" v-model="form.name"
type="text" :label="t('channels.fields.name')"
variant="outlined"
hide-details
/> />
</label>
</div> </div>
<div class="panel-actions"> <div class="panel-actions">

View File

@@ -280,26 +280,23 @@
</div> </div>
<div class="form-grid"> <div class="form-grid">
<label class="field field-wide"> <v-text-field
<span>Client name</span>
<input
v-model="form.name" v-model="form.name"
type="text" class="field-wide"
label="Client name"
:disabled="clientsStore.isUpdating" :disabled="clientsStore.isUpdating"
variant="outlined"
hide-details
/> />
</label>
<label class="field"> <v-select
<span>Status</span>
<select
v-model="form.status" v-model="form.status"
:items="['Active', 'Paused', 'Archived']"
label="Status"
:disabled="clientsStore.isUpdating" :disabled="clientsStore.isUpdating"
> variant="outlined"
<option value="Active">Active</option> hide-details
<option value="Paused">Paused</option> />
<option value="Archived">Archived</option>
</select>
</label>
<div class="field field-wide image-field"> <div class="field field-wide image-field">
<span>Client logo</span> <span>Client logo</span>
@@ -332,23 +329,22 @@
</div> </div>
</div> </div>
<label class="field"> <v-text-field
<span>Primary contact name</span>
<input
v-model="form.primaryContactName" v-model="form.primaryContactName"
type="text" label="Primary contact name"
:disabled="clientsStore.isUpdating" :disabled="clientsStore.isUpdating"
variant="outlined"
hide-details
/> />
</label>
<label class="field"> <v-text-field
<span>Primary contact email</span>
<input
v-model="form.primaryContactEmail" v-model="form.primaryContactEmail"
type="email" label="Primary contact email"
:disabled="clientsStore.isUpdating" :disabled="clientsStore.isUpdating"
type="email"
variant="outlined"
hide-details
/> />
</label>
<div class="field field-wide image-field"> <div class="field field-wide image-field">
<span>Primary contact portrait</span> <span>Primary contact portrait</span>

View File

@@ -101,52 +101,53 @@
</div> </div>
<div class="form-grid"> <div class="form-grid">
<label class="field field-wide"> <v-text-field
<span>{{ t('clients.fields.name') }}</span>
<input
v-model="form.name" v-model="form.name"
type="text" class="field-wide"
:label="t('clients.fields.name')"
:disabled="clientsStore.isCreating" :disabled="clientsStore.isCreating"
variant="outlined"
hide-details
/> />
</label>
<label class="field field-wide"> <v-text-field
<span>{{ t('clients.fields.portraitUrl') }}</span>
<input
v-model="form.portraitUrl" v-model="form.portraitUrl"
type="url" class="field-wide"
placeholder="https://..." :label="t('clients.fields.portraitUrl')"
:disabled="clientsStore.isCreating" :disabled="clientsStore.isCreating"
placeholder="https://..."
type="url"
variant="outlined"
hide-details
/> />
</label>
<label class="field"> <v-text-field
<span>{{ t('clients.fields.primaryContactName') }}</span>
<input
v-model="form.primaryContactName" v-model="form.primaryContactName"
type="text" :label="t('clients.fields.primaryContactName')"
:disabled="clientsStore.isCreating" :disabled="clientsStore.isCreating"
variant="outlined"
hide-details
/> />
</label>
<label class="field"> <v-text-field
<span>{{ t('clients.fields.primaryContactEmail') }}</span>
<input
v-model="form.primaryContactEmail" v-model="form.primaryContactEmail"
:label="t('clients.fields.primaryContactEmail')"
:disabled="clientsStore.isCreating"
type="email" type="email"
:disabled="clientsStore.isCreating" variant="outlined"
hide-details
/> />
</label>
<label class="field field-wide"> <v-text-field
<span>{{ t('clients.fields.primaryContactPortraitUrl') }}</span>
<input
v-model="form.primaryContactPortraitUrl" v-model="form.primaryContactPortraitUrl"
type="url" class="field-wide"
placeholder="https://..." :label="t('clients.fields.primaryContactPortraitUrl')"
:disabled="clientsStore.isCreating" :disabled="clientsStore.isCreating"
placeholder="https://..."
type="url"
variant="outlined"
hide-details
/> />
</label>
</div> </div>
<div class="panel-actions"> <div class="panel-actions">

View File

@@ -1,5 +1,5 @@
<script setup> <script setup>
import { computed, reactive, ref } from 'vue'; import { computed, reactive } from 'vue';
import { mdiAt, mdiClose, mdiImagePlusOutline, mdiLockOutline, mdiSend } from '@mdi/js'; import { mdiAt, mdiClose, mdiImagePlusOutline, mdiLockOutline, mdiSend } from '@mdi/js';
import AppAvatar from '@/components/AppAvatar.vue'; import AppAvatar from '@/components/AppAvatar.vue';
import { useUserProfileStore } from '@/features/user-profile/stores/userProfileStore.js'; import { useUserProfileStore } from '@/features/user-profile/stores/userProfileStore.js';
@@ -25,7 +25,6 @@
const emit = defineEmits(['submit-comment', 'cancel-reply']); const emit = defineEmits(['submit-comment', 'cancel-reply']);
const userProfileStore = useUserProfileStore(); const userProfileStore = useUserProfileStore();
const mediaFileInput = ref(null);
const form = reactive({ const form = reactive({
body: '', body: '',
@@ -73,25 +72,15 @@
form.isInternal = false; form.isInternal = false;
form.mediaFile = null; form.mediaFile = null;
form.showMentionPicker = false; form.showMentionPicker = false;
if (mediaFileInput.value) {
mediaFileInput.value.value = '';
}
} }
function openMediaPicker() { function selectMediaFile(file) {
mediaFileInput.value?.click(); form.mediaFile = Array.isArray(file) ? file[0] ?? null : file;
form.showMentionPicker = false; form.showMentionPicker = false;
} }
function selectMediaFile(event) {
form.mediaFile = event.target.files?.[0] ?? null;
}
function clearMediaFile() { function clearMediaFile() {
form.mediaFile = null; form.mediaFile = null;
if (mediaFileInput.value) {
mediaFileInput.value.value = '';
}
} }
function toggleMentionPicker() { function toggleMentionPicker() {
@@ -143,11 +132,15 @@
size="md" size="md"
/> />
<textarea <v-textarea
v-model="form.body" v-model="form.body"
class="comment-textarea" class="comment-textarea"
:placeholder="replyTarget ? 'Write a reply...' : 'Write a comment...'" :placeholder="replyTarget ? 'Write a reply...' : 'Write a comment...'"
></textarea> variant="outlined"
hide-details
auto-grow
rows="3"
/>
</div> </div>
<div <div
@@ -193,32 +186,27 @@
<div class="comment-composer-toolbar"> <div class="comment-composer-toolbar">
<div class="comment-tool-actions"> <div class="comment-tool-actions">
<label <div
class="icon-tool-button internal-toggle" class="icon-tool-button internal-toggle"
:class="{ active: form.isInternal }" :class="{ active: form.isInternal }"
title="Internal comment" title="Internal comment"
> >
<input <v-checkbox-btn
v-model="form.isInternal" v-model="form.isInternal"
type="checkbox" density="compact"
/> />
<v-icon :icon="mdiLockOutline" /> <v-icon :icon="mdiLockOutline" />
</label> </div>
<button <v-file-input
class="icon-tool-button" v-model="form.mediaFile"
type="button" class="media-file-control"
title="Upload media from computer" 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" accept="image/png,image/jpeg,image/jpg"
@change="selectMediaFile" :prepend-icon="mdiImagePlusOutline"
density="compact"
variant="outlined"
hide-details
@update:model-value="selectMediaFile"
/> />
<button <button
class="icon-tool-button" class="icon-tool-button"

View File

@@ -787,40 +787,31 @@
</div> </div>
<div class="form-grid"> <div class="form-grid">
<label class="field"> <v-text-field
<span>Title</span>
<input
v-model="form.title" v-model="form.title"
type="text" label="Title"
variant="outlined"
hide-details
/> />
</label>
<label class="field"> <v-select
<span>Campaign</span> v-model="form.campaignId"
<select v-model="form.campaignId"> :items="availableCampaigns"
<option label="Campaign"
disabled item-title="name"
value="" item-value="id"
> placeholder="Select a campaign"
Select a campaign variant="outlined"
</option> hide-details
<option />
v-for="campaign in availableCampaigns"
:key="campaign.id"
:value="campaign.id"
>
{{ campaign.name }}
</option>
</select>
</label>
<label class="field"> <v-text-field
<span>Due date</span>
<input
v-model="form.dueDate" v-model="form.dueDate"
label="Due date"
type="date" type="date"
variant="outlined"
hide-details
/> />
</label>
<div class="date-context field-wide"> <div class="date-context field-wide">
<div class="date-context-days"> <div class="date-context-days">
@@ -864,19 +855,23 @@
</div> </div>
</div> </div>
<label class="field field-wide"> <v-text-field
<span>Change summary</span>
<input
v-model="form.changeSummary" v-model="form.changeSummary"
type="text" class="field-wide"
label="Change summary"
placeholder="What changed in this revision?" placeholder="What changed in this revision?"
variant="outlined"
hide-details
/> />
</label>
<label class="field field-wide"> <v-textarea
<span>Shared brief / base caption</span> v-model="form.body"
<textarea v-model="form.body"></textarea> class="field-wide"
</label> label="Shared brief / base caption"
rows="4"
variant="outlined"
hide-details
/>
<label class="field field-wide"> <label class="field field-wide">
<span>Shared hashtags</span> <span>Shared hashtags</span>
@@ -899,11 +894,13 @@
{{ tag.startsWith('#') ? tag : `#${tag}` }} {{ tag.startsWith('#') ? tag : `#${tag}` }}
</v-chip> </v-chip>
</div> </div>
<input <v-text-field
v-model="form.hashtags" v-model="form.hashtags"
type="text"
class="hashtags-inline-input" class="hashtags-inline-input"
placeholder="#launch #campaign #brand" placeholder="#launch #campaign #brand"
density="compact"
variant="plain"
hide-details
/> />
</div> </div>
</label> </label>
@@ -961,63 +958,61 @@
</div> </div>
<div class="form-grid compact-grid"> <div class="form-grid compact-grid">
<label class="field"> <v-text-field
<span>Network</span>
<input
v-model="placement.network" v-model="placement.network"
type="text" label="Network"
placeholder="Instagram, YouTube..." placeholder="Instagram, YouTube..."
variant="outlined"
hide-details
/> />
</label>
<label class="field"> <v-select
<span>Channel</span>
<select
v-model="placement.channelId" v-model="placement.channelId"
@change="syncPlacementChannel(placement, placement.channelId)" :items="availableChannels.map(channel => ({
> title: `${channel.name}${channel.network ? ` · ${channel.network}` : ''}`,
<option value="">Select a configured channel</option> value: channel.id,
<option }))"
v-for="channel in availableChannels" label="Channel"
:key="channel.id" placeholder="Select a configured channel"
:value="channel.id" variant="outlined"
> hide-details
{{ channel.name }}{{ channel.network ? ` · ${channel.network}` : '' }} clearable
</option> @update:model-value="syncPlacementChannel(placement, $event)"
</select> />
</label>
<label class="field"> <v-text-field
<span>Channel name</span>
<input
v-model="placement.channelName" v-model="placement.channelName"
type="text" label="Channel name"
placeholder="IG Feed, YouTube Main..." placeholder="IG Feed, YouTube Main..."
variant="outlined"
hide-details
/> />
</label>
<label class="field"> <v-text-field
<span>Variant label</span>
<input
v-model="placement.variantLabel" v-model="placement.variantLabel"
type="text" label="Variant label"
placeholder="Reel version, Shorts version..." placeholder="Reel version, Shorts version..."
variant="outlined"
hide-details
/> />
</label>
<label class="field field-wide"> <v-textarea
<span>Channel-specific caption</span> v-model="placement.message"
<textarea v-model="placement.message"></textarea> class="field-wide"
</label> label="Channel-specific caption"
rows="3"
variant="outlined"
hide-details
/>
<label class="field field-wide"> <v-text-field
<span>Channel-specific hashtags</span>
<input
v-model="placement.hashtags" v-model="placement.hashtags"
type="text" class="field-wide"
label="Channel-specific hashtags"
placeholder="#product #launch" placeholder="#product #launch"
variant="outlined"
hide-details
/> />
</label>
</div> </div>
<div class="media-section"> <div class="media-section">
@@ -1042,32 +1037,30 @@
class="media-card" class="media-card"
> >
<div class="form-grid compact-grid"> <div class="form-grid compact-grid">
<label class="field"> <v-select
<span>Media type</span> v-model="media.mediaType"
<select v-model="media.mediaType"> :items="['Image', 'Video', 'Document']"
<option value="Image">Image</option> label="Media type"
<option value="Video">Video</option> variant="outlined"
<option value="Document">Document</option> hide-details
</select> />
</label>
<label class="field"> <v-text-field
<span>Label</span>
<input
v-model="media.label" v-model="media.label"
type="text" label="Label"
placeholder="Cover image, YouTube video..." placeholder="Cover image, YouTube video..."
variant="outlined"
hide-details
/> />
</label>
<label class="field field-wide"> <v-text-field
<span>Media URL / reference</span>
<input
v-model="media.url" v-model="media.url"
type="text" class="field-wide"
label="Media URL / reference"
placeholder="Google Drive link or asset URL" placeholder="Google Drive link or asset URL"
variant="outlined"
hide-details
/> />
</label>
</div> </div>
<button <button
@@ -1170,46 +1163,43 @@
<template v-else-if="activeProductionTab === 'assets'"> <template v-else-if="activeProductionTab === 'assets'">
<div class="panel-stack asset-form"> <div class="panel-stack asset-form">
<label class="field"> <v-select
<span>Type</span> v-model="assetForm.assetType"
<select v-model="assetForm.assetType"> :items="['Image', 'Video', 'Document', 'Other']"
<option value="Image">Image</option> label="Type"
<option value="Video">Video</option> variant="outlined"
<option value="Document">Document</option> hide-details
<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> <v-text-field
<label class="field field-wide"> v-model="assetForm.displayName"
<span>Google Drive link</span> label="Name"
<input placeholder="Final reel, cover image..."
variant="outlined"
hide-details
/>
<v-text-field
v-model="assetForm.googleDriveLink" v-model="assetForm.googleDriveLink"
class="field-wide"
label="Google Drive link"
type="url" type="url"
placeholder="https://drive.google.com/..." placeholder="https://drive.google.com/..."
variant="outlined"
hide-details
/> />
</label> <v-text-field
<label class="field">
<span>File id</span>
<input
v-model="assetForm.googleDriveFileId" v-model="assetForm.googleDriveFileId"
type="text" label="File id"
placeholder="Optional if link includes it" placeholder="Optional if link includes it"
variant="outlined"
hide-details
/> />
</label> <v-text-field
<label class="field">
<span>Preview URL</span>
<input
v-model="assetForm.previewUrl" v-model="assetForm.previewUrl"
label="Preview URL"
type="url" type="url"
variant="outlined"
hide-details
/> />
</label>
<button <button
class="primary-button field-wide" class="primary-button field-wide"
:disabled="detailStore.actions.asset" :disabled="detailStore.actions.asset"
@@ -1255,22 +1245,23 @@
</div> </div>
<div class="panel-stack compact-form"> <div class="panel-stack compact-form">
<label class="field field-wide"> <v-text-field
<span>New revision reference</span>
<input
v-model="assetRevisionForm(asset.id).sourceReference" v-model="assetRevisionForm(asset.id).sourceReference"
class="field-wide"
label="New revision reference"
type="url" type="url"
placeholder="Updated Drive link or production reference" placeholder="Updated Drive link or production reference"
variant="outlined"
hide-details
/> />
</label> <v-text-field
<label class="field field-wide">
<span>Notes</span>
<input
v-model="assetRevisionForm(asset.id).notes" v-model="assetRevisionForm(asset.id).notes"
type="text" class="field-wide"
label="Notes"
placeholder="What changed?" placeholder="What changed?"
variant="outlined"
hide-details
/> />
</label>
<button <button
class="secondary-button" class="secondary-button"
:disabled="detailStore.actions.assetRevision" :disabled="detailStore.actions.assetRevision"

View File

@@ -1302,42 +1302,48 @@
</button> </button>
</div> </div>
<div class="scope-row"> <v-radio-group
<label v-model="customCalendarForm.scope"
class="scope-row"
inline
hide-details
>
<v-radio
v-for="option in addScopeOptions" v-for="option in addScopeOptions"
:key="option.value" :key="option.value"
class="scope-option" :label="option.label"
>
<input
v-model="customCalendarForm.scope"
type="radio"
:value="option.value" :value="option.value"
> />
<span>{{ option.label }}</span> </v-radio-group>
</label>
</div>
<div <div
v-if="activeAddMode === 'catalog'" v-if="activeAddMode === 'catalog'"
class="catalog-panel" class="catalog-panel"
> >
<div class="catalog-search"> <div class="catalog-search">
<input <v-text-field
v-model="catalogFilters.search" v-model="catalogFilters.search"
density="compact"
variant="outlined"
hide-details
type="search" type="search"
:placeholder="t('contentItems.calendar.searchCatalog')" :placeholder="t('contentItems.calendar.searchCatalog')"
> />
<input <v-text-field
v-model="catalogFilters.country" v-model="catalogFilters.country"
type="text" density="compact"
variant="outlined"
hide-details
maxlength="2" maxlength="2"
:placeholder="t('contentItems.calendar.country')" :placeholder="t('contentItems.calendar.country')"
> />
<input <v-text-field
v-model="catalogFilters.category" v-model="catalogFilters.category"
type="text" density="compact"
variant="outlined"
hide-details
:placeholder="t('contentItems.calendar.category')" :placeholder="t('contentItems.calendar.category')"
> />
<button <button
class="text-button" class="text-button"
type="button" type="button"
@@ -1372,31 +1378,41 @@
</div> </div>
</div> </div>
<form <v-form
v-else v-else
class="custom-calendar-form" class="custom-calendar-form"
@submit.prevent="addCustomSource" @submit.prevent="addCustomSource"
> >
<input <v-text-field
v-model="customCalendarForm.title" v-model="customCalendarForm.title"
type="text" density="compact"
variant="outlined"
hide-details
:placeholder="t('contentItems.calendar.calendarName')" :placeholder="t('contentItems.calendar.calendarName')"
> />
<input <v-text-field
v-model="customCalendarForm.sourceUrl" v-model="customCalendarForm.sourceUrl"
type="url" type="url"
density="compact"
variant="outlined"
hide-details
:placeholder="t('contentItems.calendar.icsUrl')" :placeholder="t('contentItems.calendar.icsUrl')"
> />
<div class="custom-form-row"> <div class="custom-form-row">
<input <v-text-field
v-model="customCalendarForm.color" v-model="customCalendarForm.color"
type="color" type="color"
> density="compact"
<input variant="outlined"
hide-details
/>
<v-text-field
v-model="customCalendarForm.category" v-model="customCalendarForm.category"
type="text" density="compact"
variant="outlined"
hide-details
:placeholder="t('contentItems.calendar.category')" :placeholder="t('contentItems.calendar.category')"
> />
<button <button
class="text-button" class="text-button"
type="submit" type="submit"
@@ -1405,7 +1421,7 @@
<span>{{ t('contentItems.calendar.addCalendar') }}</span> <span>{{ t('contentItems.calendar.addCalendar') }}</span>
</button> </button>
</div> </div>
</form> </v-form>
<p <p
v-if="addCalendarError || calendarStore.error" v-if="addCalendarError || calendarStore.error"

View File

@@ -317,7 +317,7 @@
</div> </div>
</div> </div>
<form <v-form
class="comment-form" class="comment-form"
@submit.prevent="submitComment" @submit.prevent="submitComment"
> >
@@ -336,7 +336,7 @@
> >
{{ feedbackStore.isCommenting ? t('feedback.review.detail.commenting') : t('feedback.review.detail.addComment') }} {{ feedbackStore.isCommenting ? t('feedback.review.detail.commenting') : t('feedback.review.detail.addComment') }}
</button> </button>
</form> </v-form>
</section> </section>
</main> </main>

View File

@@ -100,14 +100,16 @@
</section> </section>
<section class="filter-panel"> <section class="filter-panel">
<label class="filter-search"> <v-text-field
<v-icon :icon="mdiMagnify" />
<input
v-model="feedbackStore.filters.search" v-model="feedbackStore.filters.search"
:label="t('feedback.review.filters.search')"
:prepend-inner-icon="mdiMagnify"
density="compact"
variant="outlined"
hide-details
clearable
type="search" type="search"
:placeholder="t('feedback.review.filters.search')"
/> />
</label>
<v-select <v-select
v-model="feedbackStore.filters.type" v-model="feedbackStore.filters.type"
@@ -139,32 +141,40 @@
clearable clearable
/> />
<input <v-text-field
v-model="feedbackStore.filters.reporter" v-model="feedbackStore.filters.reporter"
class="field" :label="t('feedback.review.filters.reporter')"
type="text" density="compact"
:placeholder="t('feedback.review.filters.reporter')" variant="outlined"
hide-details
clearable
/> />
<input <v-text-field
v-model="feedbackStore.filters.workspace" v-model="feedbackStore.filters.workspace"
class="field" :label="t('feedback.review.filters.workspace')"
type="text" density="compact"
:placeholder="t('feedback.review.filters.workspace')" variant="outlined"
hide-details
clearable
/> />
<input <v-text-field
v-model="feedbackStore.filters.fromDate" v-model="feedbackStore.filters.fromDate"
class="field" :label="t('feedback.review.filters.fromDate')"
density="compact"
variant="outlined"
hide-details
type="date" type="date"
:aria-label="t('feedback.review.filters.fromDate')"
/> />
<input <v-text-field
v-model="feedbackStore.filters.toDate" v-model="feedbackStore.filters.toDate"
class="field" :label="t('feedback.review.filters.toDate')"
density="compact"
variant="outlined"
hide-details
type="date" type="date"
:aria-label="t('feedback.review.filters.toDate')"
/> />
<v-select <v-select

View File

@@ -207,18 +207,20 @@
size="lg" size="lg"
/> />
</button> </button>
<form <v-form
v-if="organization && isEditingName" v-if="organization && isEditingName"
class="title-edit-form" class="title-edit-form"
@submit.prevent="submitProfile" @submit.prevent="submitProfile"
> >
<input <v-text-field
v-model="profileForm.name" v-model="profileForm.name"
type="text"
maxlength="256"
autocomplete="organization"
:aria-label="t('organizationSettings.fields.name')" :aria-label="t('organizationSettings.fields.name')"
> autocomplete="organization"
density="compact"
hide-details
maxlength="256"
variant="outlined"
/>
<button <button
class="icon-action" class="icon-action"
type="submit" type="submit"
@@ -238,7 +240,7 @@
> >
<v-icon :icon="mdiClose" /> <v-icon :icon="mdiClose" />
</button> </button>
</form> </v-form>
<div <div
v-else v-else
class="title-row" class="title-row"
@@ -335,31 +337,26 @@
v-if="activeSection.key === 'members'" v-if="activeSection.key === 'members'"
class="table-list" class="table-list"
> >
<form <v-form
class="settings-form invite-form" class="settings-form invite-form"
@submit.prevent="submitMember" @submit.prevent="submitMember"
> >
<label> <v-text-field
<span>{{ t('organizationSettings.fields.memberEmail') }}</span>
<input
v-model="memberForm.email" v-model="memberForm.email"
type="email" :label="t('organizationSettings.fields.memberEmail')"
maxlength="256"
autocomplete="email" autocomplete="email"
> maxlength="256"
</label> type="email"
<label> variant="outlined"
<span>{{ t('organizationSettings.fields.memberRole') }}</span> hide-details
<select v-model="memberForm.role"> />
<option <v-select
v-for="role in memberRoleOptions" v-model="memberForm.role"
:key="role" :items="memberRoleOptions.map(role => ({ title: t(`organizationSettings.roles.${role}`, role), value: role }))"
:value="role" :label="t('organizationSettings.fields.memberRole')"
> variant="outlined"
{{ t(`organizationSettings.roles.${role}`, role) }} hide-details
</option> />
</select>
</label>
<div class="form-actions"> <div class="form-actions">
<button <button
class="primary-action" class="primary-action"
@@ -369,7 +366,7 @@
{{ organizationStore.isAddingMember ? t('organizationSettings.addingMember') : t('organizationSettings.addMember') }} {{ organizationStore.isAddingMember ? t('organizationSettings.addingMember') : t('organizationSettings.addMember') }}
</button> </button>
</div> </div>
</form> </v-form>
<div <div
v-for="member in organization.members" v-for="member in organization.members"
:key="member.userId" :key="member.userId"

View File

@@ -206,51 +206,48 @@
{{ settingsStatus }} {{ settingsStatus }}
</div> </div>
<form <v-form
class="form-stack" class="form-stack"
@submit.prevent="submitSettings" @submit.prevent="submitSettings"
> >
<div class="details-grid"> <div class="details-grid">
<label class="field"> <v-text-field
<span>{{ t('userSettings.firstname') }}</span>
<input
v-model="form.firstname" v-model="form.firstname"
type="text" :label="t('userSettings.firstname')"
autocomplete="given-name" autocomplete="given-name"
:disabled="userProfileStore.isUpdating" :disabled="userProfileStore.isUpdating"
variant="outlined"
hide-details
/> />
</label>
<label class="field"> <v-text-field
<span>{{ t('userSettings.lastname') }}</span>
<input
v-model="form.lastname" v-model="form.lastname"
type="text" :label="t('userSettings.lastname')"
autocomplete="family-name" autocomplete="family-name"
:disabled="userProfileStore.isUpdating" :disabled="userProfileStore.isUpdating"
variant="outlined"
hide-details
/> />
</label>
<label class="field"> <v-text-field
<span>{{ t('userSettings.alias') }}</span>
<input
v-model="form.alias" v-model="form.alias"
type="text" :label="t('userSettings.alias')"
autocomplete="nickname" autocomplete="nickname"
:placeholder="fullname" :placeholder="fullname"
:disabled="userProfileStore.isUpdating" :disabled="userProfileStore.isUpdating"
variant="outlined"
hide-details
/> />
</label>
<label class="field"> <v-text-field
<span>{{ t('userSettings.email') }}</span>
<input
v-model="form.email" v-model="form.email"
type="email" :label="t('userSettings.email')"
autocomplete="email" autocomplete="email"
:disabled="userProfileStore.isUpdating" :disabled="userProfileStore.isUpdating"
type="email"
variant="outlined"
hide-details
/> />
</label>
</div> </div>
<div class="form-actions"> <div class="form-actions">
@@ -262,7 +259,7 @@
{{ userProfileStore.isUpdating ? t('common.saving') : t('userSettings.saveDetails') }} {{ userProfileStore.isUpdating ? t('common.saving') : t('userSettings.saveDetails') }}
</button> </button>
</div> </div>
</form> </v-form>
</div> </div>
<div class="panel"> <div class="panel">

View File

@@ -1,4 +1,5 @@
<script setup> <script setup>
import { computed } from 'vue';
import { import {
mdiArrowDown, mdiArrowDown,
mdiArrowUp, mdiArrowUp,
@@ -40,6 +41,22 @@
]; ];
const membershipOptions = ['Team', 'Client']; const membershipOptions = ['Team', 'Client'];
const targetTypes = ['Role', 'Membership', 'Member']; const targetTypes = ['Role', 'Membership', 'Member'];
const roleItems = computed(() => roleOptions.map(role => ({
title: props.labels.roles[role],
value: role,
})));
const membershipItems = computed(() => membershipOptions.map(membership => ({
title: props.labels.memberships[membership],
value: membership,
})));
const targetTypeItems = computed(() => targetTypes.map(targetType => ({
title: props.labels.targetTypes[targetType],
value: targetType,
})));
const memberItems = computed(() => props.members.map(member => ({
title: `${member.displayName} - ${member.email}`,
value: member.id,
})));
function emitSteps(steps) { function emitSteps(steps) {
emit('update:modelValue', steps.map((step, index) => ({ emit('update:modelValue', steps.map((step, index) => ({
@@ -101,13 +118,8 @@
.filter(Boolean); .filter(Boolean);
} }
function updateMemberTargets(index, selectedOptions) { function updateMemberTargets(index, selectedMemberIds) {
const targetValue = Array.from(selectedOptions) updateStep(index, { targetValue: selectedMemberIds.filter(Boolean).join(',') });
.map(option => option.value)
.filter(Boolean)
.join(',');
updateStep(index, { targetValue });
} }
function moveStep(index, offset) { function moveStep(index, offset) {
@@ -198,13 +210,14 @@
</div> </div>
<div class="approval-step-fields"> <div class="approval-step-fields">
<label class="field"> <div class="field">
<span>{{ labels.fields.name }}</span> <v-text-field
<input :model-value="step.name"
:value="step.name" :label="labels.fields.name"
type="text"
:disabled="disabled" :disabled="disabled"
@input="updateStep(index, { name: $event.target.value })" variant="outlined"
hide-details
@update:model-value="updateStep(index, { name: $event })"
/> />
<small <small
v-if="errors[index]?.name" v-if="errors[index]?.name"
@@ -212,73 +225,54 @@
> >
{{ errors[index].name }} {{ errors[index].name }}
</small> </small>
</label> </div>
<label class="field"> <v-select
<span>{{ labels.fields.targetType }}</span> :model-value="step.targetType"
<select :items="targetTypeItems"
:value="step.targetType" :label="labels.fields.targetType"
:disabled="disabled" :disabled="disabled"
@change="updateStep(index, { targetType: $event.target.value })" variant="outlined"
> hide-details
<option @update:model-value="updateStep(index, { targetType: $event })"
v-for="targetType in targetTypes" />
:key="targetType"
:value="targetType"
>
{{ labels.targetTypes[targetType] }}
</option>
</select>
</label>
<label class="field"> <div class="field">
<span>{{ labels.fields.targetValue }}</span> <v-select
<select
v-if="step.targetType === 'Role'" v-if="step.targetType === 'Role'"
:value="step.targetValue" :model-value="step.targetValue"
:items="roleItems"
:label="labels.fields.targetValue"
:disabled="disabled" :disabled="disabled"
@change="updateStep(index, { targetValue: $event.target.value })" variant="outlined"
> hide-details
<option @update:model-value="updateStep(index, { targetValue: $event })"
v-for="role in roleOptions" />
:key="role"
:value="role"
>
{{ labels.roles[role] }}
</option>
</select>
<select <v-select
v-else-if="step.targetType === 'Membership'" v-else-if="step.targetType === 'Membership'"
:value="step.targetValue" :model-value="step.targetValue"
:items="membershipItems"
:label="labels.fields.targetValue"
:disabled="disabled" :disabled="disabled"
@change="updateStep(index, { targetValue: $event.target.value })" variant="outlined"
> hide-details
<option @update:model-value="updateStep(index, { targetValue: $event })"
v-for="membership in membershipOptions" />
:key="membership"
:value="membership"
>
{{ labels.memberships[membership] }}
</option>
</select>
<select <v-select
v-else v-else
:value="getSelectedMemberIds(step)" :model-value="getSelectedMemberIds(step)"
:items="memberItems"
:label="labels.fields.targetValue"
:disabled="disabled" :disabled="disabled"
multiple multiple
size="5" chips
@change="updateMemberTargets(index, $event.target.selectedOptions)" closable-chips
> variant="outlined"
<option hide-details
v-for="member in members" @update:model-value="updateMemberTargets(index, $event)"
:key="member.id" />
:value="member.id"
>
{{ member.displayName }} - {{ member.email }}
</option>
</select>
<small <small
v-if="step.targetType === 'Member'" v-if="step.targetType === 'Member'"
class="field-help" class="field-help"
@@ -292,17 +286,19 @@
> >
{{ errors[index].targetValue }} {{ errors[index].targetValue }}
</small> </small>
</label> </div>
<label class="field"> <div class="field">
<span>{{ labels.fields.requiredApproverCount }}</span> <v-text-field
<input :model-value="step.requiredApproverCount"
:value="step.requiredApproverCount" :label="labels.fields.requiredApproverCount"
type="number" type="number"
min="1" min="1"
step="1" step="1"
:disabled="disabled" :disabled="disabled"
@input="updateStep(index, { requiredApproverCount: Number($event.target.value) })" variant="outlined"
hide-details
@update:model-value="updateStep(index, { requiredApproverCount: Number($event) })"
/> />
<small <small
v-if="errors[index]?.requiredApproverCount" v-if="errors[index]?.requiredApproverCount"
@@ -310,7 +306,7 @@
> >
{{ errors[index].requiredApproverCount }} {{ errors[index].requiredApproverCount }}
</small> </small>
</label> </div>
</div> </div>
</section> </section>
</div> </div>

View File

@@ -13,38 +13,23 @@
}, },
}); });
const emit = defineEmits(['update:modelValue']);
const timeZoneOptions = computed(() => getTimeZoneOptions(props.modelValue)); const timeZoneOptions = computed(() => getTimeZoneOptions(props.modelValue));
function updateValue(event) {
emit('update:modelValue', event.target.value);
}
</script> </script>
<template> <template>
<select <v-select
class="time-zone-select" :model-value="modelValue"
:value="modelValue" :items="timeZoneOptions"
:disabled="disabled" :disabled="disabled"
@change="updateValue" item-title="label"
> item-value="value"
<option density="compact"
v-for="timeZone in timeZoneOptions" variant="outlined"
:key="timeZone.value" hide-details
:value="timeZone.value" @update:model-value="$emit('update:modelValue', $event)"
> />
{{ timeZone.label }}
</option>
</select>
</template> </template>
<style scoped> <style scoped>
@reference "@/assets/main.css"; @reference "@/assets/main.css";
.time-zone-select {
@apply rounded-[1rem] border px-4 py-3 text-sm;
background: #fffdf8;
border-color: rgba(23, 32, 51, 0.1);
color: #172033;
outline: none;
}
</style> </style>

View File

@@ -82,43 +82,35 @@
{{ formError }} {{ formError }}
</div> </div>
<form <v-form
class="form-grid" class="form-grid"
@submit.prevent="submitForm" @submit.prevent="submitForm"
> >
<label class="field field-wide"> <v-text-field
<span>{{ t('workspaceCreate.fields.name') }}</span>
<input
v-model="form.name" v-model="form.name"
type="text" class="field-wide"
:label="t('workspaceCreate.fields.name')"
:placeholder="t('workspaceCreate.fields.namePlaceholder')" :placeholder="t('workspaceCreate.fields.namePlaceholder')"
:disabled="workspaceStore.isCreating" :disabled="workspaceStore.isCreating"
variant="outlined"
hide-details
/> />
</label>
<label class="field"> <v-select
<span>{{ t('workspaceCreate.fields.organization') }}</span>
<select
v-model="selectedOrganizationId" v-model="selectedOrganizationId"
:items="organizationStore.organizations"
:label="t('workspaceCreate.fields.organization')"
:disabled="workspaceStore.isCreating || organizationStore.organizations.length <= 1" :disabled="workspaceStore.isCreating || organizationStore.organizations.length <= 1"
> item-title="name"
<option item-value="id"
v-for="organization in organizationStore.organizations" variant="outlined"
:key="organization.id" hide-details
:value="organization.id" />
>
{{ organization.name }}
</option>
</select>
</label>
<label class="field">
<span>{{ t('workspaceCreate.fields.timeZone') }}</span>
<TimeZoneSelect <TimeZoneSelect
v-model="form.timeZone" v-model="form.timeZone"
:disabled="workspaceStore.isCreating" :disabled="workspaceStore.isCreating"
/> />
</label>
<div class="panel-actions field-wide"> <div class="panel-actions field-wide">
<button <button
@@ -137,7 +129,7 @@
{{ workspaceStore.isCreating ? t('common.creating') : t('workspaceCreate.createAction') }} {{ workspaceStore.isCreating ? t('common.creating') : t('workspaceCreate.createAction') }}
</button> </button>
</div> </div>
</form> </v-form>
</article> </article>
</section> </section>
</template> </template>

View File

@@ -460,7 +460,7 @@
{{ settingsStatus }} {{ settingsStatus }}
</div> </div>
<form <v-form
class="form-stack" class="form-stack"
@submit.prevent="submitWorkspaceSettings" @submit.prevent="submitWorkspaceSettings"
> >
@@ -496,14 +496,13 @@
</button> </button>
</div> </div>
<label class="field"> <v-text-field
<span>{{ t('workspaceSettings.fields.name') }}</span>
<input
v-model="settingsForm.name" v-model="settingsForm.name"
type="text" :label="t('workspaceSettings.fields.name')"
:disabled="workspaceStore.isUpdating" :disabled="workspaceStore.isUpdating"
variant="outlined"
hide-details
/> />
</label>
<label class="field"> <label class="field">
<span>{{ t('workspaceSettings.fields.timeZone') }}</span> <span>{{ t('workspaceSettings.fields.timeZone') }}</span>
@@ -520,7 +519,7 @@
> >
{{ workspaceStore.isUpdating ? t('common.saving') : t('workspaceSettings.general.saveAction') }} {{ workspaceStore.isUpdating ? t('common.saving') : t('workspaceSettings.general.saveAction') }}
</button> </button>
</form> </v-form>
</article> </article>
</div> </div>
@@ -535,26 +534,29 @@
<p>{{ t('workspaceSettings.inviteDescription') }}</p> <p>{{ t('workspaceSettings.inviteDescription') }}</p>
</div> </div>
<form <v-form
class="form-stack" class="form-stack"
@submit.prevent="submitInvite" @submit.prevent="submitInvite"
> >
<label class="field"> <v-text-field
<span>{{ t('workspaceSettings.fields.memberEmail') }}</span>
<input
v-model="inviteForm.email" v-model="inviteForm.email"
:label="t('workspaceSettings.fields.memberEmail')"
type="email" type="email"
variant="outlined"
hide-details
/> />
</label>
<label class="field"> <v-select
<span>{{ t('workspaceSettings.fields.memberRole') }}</span> v-model="inviteForm.role"
<select v-model="inviteForm.role"> :items="[
<option value="workspace-member">{{ t('workspaceSettings.roles.workspace-member') }}</option> { title: t('workspaceSettings.roles.workspace-member'), value: 'workspace-member' },
<option value="client">{{ t('workspaceSettings.roles.client') }}</option> { title: t('workspaceSettings.roles.client'), value: 'client' },
<option value="provider">{{ t('workspaceSettings.roles.provider') }}</option> { title: t('workspaceSettings.roles.provider'), value: 'provider' },
</select> ]"
</label> :label="t('workspaceSettings.fields.memberRole')"
variant="outlined"
hide-details
/>
<button <button
class="primary-button" class="primary-button"
@@ -562,7 +564,7 @@
> >
{{ workspaceStore.isInviting ? t('common.creating') : t('workspaceSettings.sendInvite') }} {{ workspaceStore.isInviting ? t('common.creating') : t('workspaceSettings.sendInvite') }}
</button> </button>
</form> </v-form>
</article> </article>
<article class="settings-card"> <article class="settings-card">
@@ -667,21 +669,16 @@
</div> </div>
<div class="workflow-rule-list"> <div class="workflow-rule-list">
<label class="field"> <v-select
<span>{{ t('workspaceSettings.approvals.fields.approvalMode') }}</span>
<select
v-model="settingsForm.approvalMode" v-model="settingsForm.approvalMode"
:items="approvalModeOptions"
:label="t('workspaceSettings.approvals.fields.approvalMode')"
:disabled="workspaceStore.isUpdating" :disabled="workspaceStore.isUpdating"
> item-title="label"
<option item-value="value"
v-for="option in approvalModeOptions" variant="outlined"
:key="option.value" hide-details
:value="option.value" />
>
{{ option.label }}
</option>
</select>
</label>
<div class="workflow-rule"> <div class="workflow-rule">
<strong>{{ activeApprovalModeOption.label }}</strong> <strong>{{ activeApprovalModeOption.label }}</strong>
@@ -697,41 +694,44 @@
:labels="approvalWorkflowEditorLabels" :labels="approvalWorkflowEditorLabels"
/> />
<label class="workflow-toggle"> <div class="workflow-toggle">
<input <v-checkbox
v-model="settingsForm.schedulePostsAutomaticallyOnApproval" v-model="settingsForm.schedulePostsAutomaticallyOnApproval"
type="checkbox"
:disabled="workspaceStore.isUpdating" :disabled="workspaceStore.isUpdating"
density="compact"
hide-details
/> />
<span> <span>
<strong>{{ t('workspaceSettings.approvals.fields.schedulePostsAutomaticallyOnApproval') }}</strong> <strong>{{ t('workspaceSettings.approvals.fields.schedulePostsAutomaticallyOnApproval') }}</strong>
<small>{{ t('workspaceSettings.approvals.fieldHelp.schedulePostsAutomaticallyOnApproval') }}</small> <small>{{ t('workspaceSettings.approvals.fieldHelp.schedulePostsAutomaticallyOnApproval') }}</small>
</span> </span>
</label> </div>
<label class="workflow-toggle"> <div class="workflow-toggle">
<input <v-checkbox
v-model="settingsForm.lockContentAfterApproval" v-model="settingsForm.lockContentAfterApproval"
type="checkbox"
:disabled="workspaceStore.isUpdating" :disabled="workspaceStore.isUpdating"
density="compact"
hide-details
/> />
<span> <span>
<strong>{{ t('workspaceSettings.approvals.fields.lockContentAfterApproval') }}</strong> <strong>{{ t('workspaceSettings.approvals.fields.lockContentAfterApproval') }}</strong>
<small>{{ t('workspaceSettings.approvals.fieldHelp.lockContentAfterApproval') }}</small> <small>{{ t('workspaceSettings.approvals.fieldHelp.lockContentAfterApproval') }}</small>
</span> </span>
</label> </div>
<label class="workflow-toggle"> <div class="workflow-toggle">
<input <v-checkbox
v-model="settingsForm.sendAutomaticApprovalReminders" v-model="settingsForm.sendAutomaticApprovalReminders"
type="checkbox"
:disabled="workspaceStore.isUpdating" :disabled="workspaceStore.isUpdating"
density="compact"
hide-details
/> />
<span> <span>
<strong>{{ t('workspaceSettings.approvals.fields.sendAutomaticApprovalReminders') }}</strong> <strong>{{ t('workspaceSettings.approvals.fields.sendAutomaticApprovalReminders') }}</strong>
<small>{{ t('workspaceSettings.approvals.fieldHelp.sendAutomaticApprovalReminders') }}</small> <small>{{ t('workspaceSettings.approvals.fieldHelp.sendAutomaticApprovalReminders') }}</small>
</span> </span>
</label> </div>
<button <button
class="primary-button" class="primary-button"

View File

@@ -284,25 +284,25 @@
ref="searchRef" ref="searchRef"
class="sidebar-search-wrap" class="sidebar-search-wrap"
> >
<label <div
class="sidebar-search" class="sidebar-search"
:class="{ 'sidebar-search-open': isSearchOpen }" :class="{ 'sidebar-search-open': isSearchOpen }"
:title="!isExpanded ? 'Search' : null" :title="!isExpanded ? 'Search' : null"
@click="openCollapsedSearch" @click="openCollapsedSearch"
> >
<v-icon <v-text-field
:icon="mdiMagnify"
class="sidebar-search-icon"
/>
<input
v-if="isExpanded" v-if="isExpanded"
v-model="searchQuery" v-model="searchQuery"
type="search"
class="sidebar-search-input" class="sidebar-search-input"
:prepend-inner-icon="mdiMagnify"
placeholder="Search" placeholder="Search"
density="compact"
variant="plain"
hide-details
type="search"
@focus="isSearchFocused = true" @focus="isSearchFocused = true"
/> />
</label> </div>
<div <div
v-if="isSearchPanelOpen" v-if="isSearchPanelOpen"
@@ -310,22 +310,22 @@
:class="{ 'sidebar-search-panel-collapsed': !isExpanded }" :class="{ 'sidebar-search-panel-collapsed': !isExpanded }"
:style="!isExpanded ? collapsedSearchPanelStyle : null" :style="!isExpanded ? collapsedSearchPanelStyle : null"
> >
<label <div
v-if="!isExpanded" v-if="!isExpanded"
class="sidebar-search sidebar-search-panel-input" class="sidebar-search sidebar-search-panel-input"
> >
<v-icon <v-text-field
:icon="mdiMagnify"
class="sidebar-search-icon"
/>
<input
ref="collapsedSearchInputRef" ref="collapsedSearchInputRef"
v-model="searchQuery" v-model="searchQuery"
type="search"
class="sidebar-search-input" class="sidebar-search-input"
:prepend-inner-icon="mdiMagnify"
placeholder="Search" placeholder="Search"
density="compact"
variant="plain"
hide-details
type="search"
/> />
</label> </div>
<div <div
v-if="campaignResults.length" v-if="campaignResults.length"

View File

@@ -9,11 +9,16 @@ import {
VAlert, VAlert,
VApp, VApp,
VBtn, VBtn,
VCheckbox,
VCheckboxBtn,
VDialog, VDialog,
VFileInput,
VForm, VForm,
VIcon, VIcon,
VProgressCircular, VProgressCircular,
VProgressLinear, VProgressLinear,
VRadio,
VRadioGroup,
VSelect, VSelect,
VSnackbar, VSnackbar,
VTextarea, VTextarea,
@@ -42,9 +47,14 @@ const vuetify = createVuetify({
VDialog, VDialog,
VApp, VApp,
VBtn, VBtn,
VCheckbox,
VCheckboxBtn,
VFileInput,
VProgressLinear, VProgressLinear,
VProgressCircular, VProgressCircular,
VIcon, VIcon,
VRadio,
VRadioGroup,
VSelect, VSelect,
VTextField, VTextField,
VSnackbar, VSnackbar,