FIX - refine Companies request interface

This commit is contained in:
Leon.Schmidt
2026-08-11 16:18:46 +02:00
parent c9a656264c
commit 0f282eef0c
+236 -303
View File
@@ -11,6 +11,7 @@ import {
kChip,
kDialog,
kDialogButton,
kGlass,
kIcon,
kLink,
kList,
@@ -41,7 +42,6 @@ import {
BriefcaseBusiness,
Building2,
Check,
CheckCircle2,
CircleAlert,
ClipboardList,
Clock3,
@@ -62,7 +62,15 @@ import {
UsersRound,
X,
} from 'lucide-vue-next'
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
import {
computed,
nextTick,
onBeforeUnmount,
onMounted,
reactive,
ref,
watch,
} from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useCallsStore } from '@/stores/calls'
@@ -82,7 +90,6 @@ import type {
CompanyRequestSummary,
CompanyService,
CompanySummary,
CompanyWorkFilter,
} from '@/types/companies'
import type { PhoneMedia } from '@/types/media'
import { nuiCall } from '@/utils/nui'
@@ -132,14 +139,9 @@ const screen = ref<CompaniesScreen>('root')
const requestOrigin = ref<RequestOrigin>('customer')
const search = ref('')
const selectedCategory = ref<string | null>(null)
const onlyAvailable = ref(false)
const onlyWithLocation = ref(false)
const onlyAcceptingRequests = ref(false)
const directorySort = ref<CompanyDirectoryFilters['sort']>('relevance')
const requestList = ref<CompanyRequestList>('open')
const workFilter = ref<CompanyWorkFilter>('new')
const requestSheetOpened = ref(false)
const requestConfirmOpened = ref(false)
const requestSheetContent = ref<HTMLElement | null>(null)
const requestServiceId = ref('')
const requestSubject = ref('')
const requestDescription = ref('')
@@ -170,23 +172,18 @@ let searchTimer: ReturnType<typeof setTimeout> | undefined
let toastTimer: ReturnType<typeof setTimeout> | undefined
const directoryFilters = computed<CompanyDirectoryFilters>(() => ({
acceptsRequests: onlyAcceptingRequests.value,
availability: onlyAvailable.value ? 'available' : null,
acceptsRequests: false,
availability: null,
categoryId: selectedCategory.value,
hasLocation: onlyWithLocation.value,
hasLocation: false,
search: search.value.trim(),
sort: directorySort.value,
sort: 'relevance',
}))
const availableCompanies = computed(() =>
companies.directory.filter((company) => company.availability === 'available'),
)
const filtersActive = computed(
() =>
Boolean(search.value.trim()) ||
Boolean(selectedCategory.value) ||
onlyAvailable.value ||
onlyWithLocation.value ||
onlyAcceptingRequests.value,
() => Boolean(search.value.trim()) || Boolean(selectedCategory.value),
)
const activeCompany = computed(() => companies.company)
const workCompany = computed(() => companies.workContext?.company ?? null)
@@ -196,11 +193,6 @@ const managerLogoUrl = computed(
const managerCoverUrl = computed(
() => selectedCoverMedia.value?.url ?? workCompany.value?.coverUrl ?? null,
)
const selectedService = computed(() =>
activeCompany.value?.services.find(
(service) => service.id === requestServiceId.value,
),
)
const requestProgress = computed(() => {
if (!requestServiceId.value) return 0.25
if (!requestSubject.value.trim() || !requestDescription.value.trim())
@@ -237,13 +229,6 @@ const availabilityValues: CompanyAvailability[] = [
'busy',
'closed',
]
const workFilters: CompanyWorkFilter[] = [
'new',
'assigned',
'in_progress',
'waiting_customer',
'completed',
]
const weekdayKeys = [
'monday',
'tuesday',
@@ -384,14 +369,6 @@ function requestSubtitle(request: CompanyRequestSummary): string {
return parts.join(' · ')
}
function assignmentLabel(
value: CompanyRequestSummary['assignedLabel'],
): string {
return value
? phone.t(`Apps.companies.assignmentLabels.${value}`)
: phone.t('Apps.companies.work.unassigned')
}
function messageAuthorLabel(value: 'company' | 'customer' | 'you'): string {
return phone.t(`Apps.companies.messageAuthors.${value}`)
}
@@ -416,10 +393,6 @@ function eventLabel(event: CompanyRequestEvent): string {
function resetDirectoryFilters(): void {
search.value = ''
selectedCategory.value = null
onlyAvailable.value = false
onlyWithLocation.value = false
onlyAcceptingRequests.value = false
directorySort.value = 'relevance'
void companies.loadCompanies(directoryFilters.value)
}
@@ -442,12 +415,7 @@ async function selectTab(tab: CompaniesTab): Promise<void> {
await companies.loadCompanies(directoryFilters.value)
}
if (tab === 'requests') await companies.loadMyRequests(requestList.value)
if (tab === 'work') {
const loaded = await companies.loadWorkContext()
if (loaded && companies.workContext?.authorized) {
await companies.loadWorkQueue(workFilter.value)
}
}
if (tab === 'work') await companies.loadWorkContext()
}
async function openCompany(companyId: string): Promise<void> {
@@ -576,7 +544,6 @@ async function submitRequest(): Promise<void> {
serviceId: requestServiceId.value,
subject: requestSubject.value.trim(),
})
requestConfirmOpened.value = false
if (!response.success || !response.data?.request) {
if (response.error === 'revision_conflict')
conflictDialogOpened.value = true
@@ -933,24 +900,15 @@ async function reloadConflict(): Promise<void> {
}
watch(search, queueDirectoryLoad)
watch(
[
selectedCategory,
onlyAvailable,
onlyWithLocation,
onlyAcceptingRequests,
directorySort,
],
() => void companies.loadCompanies(directoryFilters.value),
)
watch(selectedCategory, () => companies.loadCompanies(directoryFilters.value))
watch(requestList, () => {
if (activeTab.value === 'requests')
void companies.loadMyRequests(requestList.value)
})
watch(workFilter, () => {
if (activeTab.value === 'work' && companies.workContext?.authorized) {
void companies.loadWorkQueue(workFilter.value)
}
watch(requestSheetOpened, async (opened) => {
if (!opened) return
await nextTick()
requestSheetContent.value?.scrollTo({ top: 0 })
})
watch(
() => route.query.requestId,
@@ -1098,50 +1056,21 @@ onBeforeUnmount(() => {
</k-chip>
</div>
<k-card class="companies-filter-card" :content-wrap="false">
<k-list nested>
<k-list-item
:title="phone.t('Apps.companies.directory.availableNow')"
:subtitle="phone.t('Apps.companies.directory.availableNowHint')"
>
<template #after>
<k-toggle
:checked="onlyAvailable"
:aria-label="phone.t('Apps.companies.directory.availableNow')"
@change="onlyAvailable = !onlyAvailable"
/>
</template>
</k-list-item>
<k-list-item :title="phone.t('Apps.companies.directory.moreFilters')">
<template #after>
<div class="companies-inline-chips">
<k-chip
component="button"
type="button"
:class="{ 'is-selected': onlyWithLocation }"
@click="onlyWithLocation = !onlyWithLocation"
>
{{ phone.t('Apps.companies.directory.hasLocation') }}
</k-chip>
<k-chip
component="button"
type="button"
:class="{ 'is-selected': onlyAcceptingRequests }"
@click="onlyAcceptingRequests = !onlyAcceptingRequests"
>
{{ phone.t('Apps.companies.directory.acceptsRequests') }}
</k-chip>
</div>
</template>
</k-list-item>
</k-list>
</k-card>
<div v-if="companies.directoryLoading" class="companies-state">
<k-block
v-if="companies.directoryLoading"
inset
strong
class="companies-state"
>
<k-preloader />
<span>{{ phone.t('Apps.companies.loading.directory') }}</span>
</div>
<div v-else-if="companies.directoryError" class="companies-state">
</k-block>
<k-block
v-else-if="companies.directoryError"
inset
strong
class="companies-state"
>
<CircleAlert :size="34" />
<strong>{{ phone.t('Apps.companies.states.directoryError') }}</strong>
<p>{{ errorText(companies.directoryError) }}</p>
@@ -1149,8 +1078,13 @@ onBeforeUnmount(() => {
<RefreshCw :size="16" />
{{ phone.t('Apps.companies.tryAgain') }}
</k-button>
</div>
<div v-else-if="!companies.directory.length" class="companies-state">
</k-block>
<k-block
v-else-if="!companies.directory.length"
inset
strong
class="companies-state"
>
<Building2 :size="36" />
<strong>
{{
@@ -1173,9 +1107,9 @@ onBeforeUnmount(() => {
<k-button v-if="filtersActive" rounded @click="resetDirectoryFilters">
{{ phone.t('Apps.companies.directory.resetFilters') }}
</k-button>
</div>
</k-block>
<template v-else>
<template v-if="availableCompanies.length && !onlyAvailable">
<template v-if="availableCompanies.length">
<k-block-title>{{
phone.t('Apps.companies.directory.availableSection')
}}</k-block-title>
@@ -1286,19 +1220,34 @@ onBeforeUnmount(() => {
</k-segmented>
</div>
<div v-if="companies.myRequestsLoading" class="companies-state">
<k-block
v-if="companies.myRequestsLoading"
inset
strong
class="companies-state"
>
<k-preloader />
<span>{{ phone.t('Apps.companies.loading.requests') }}</span>
</div>
<div v-else-if="companies.myRequestsError" class="companies-state">
</k-block>
<k-block
v-else-if="companies.myRequestsError"
inset
strong
class="companies-state"
>
<CircleAlert :size="34" />
<strong>{{ phone.t('Apps.companies.states.requestsError') }}</strong>
<p>{{ errorText(companies.myRequestsError) }}</p>
<k-button rounded @click="companies.loadMyRequests(requestList)">
{{ phone.t('Apps.companies.tryAgain') }}
</k-button>
</div>
<div v-else-if="!companies.myRequests.length" class="companies-state">
</k-block>
<k-block
v-else-if="!companies.myRequests.length"
inset
strong
class="companies-state"
>
<ClipboardList :size="36" />
<strong>{{
phone.t(
@@ -1313,7 +1262,7 @@ onBeforeUnmount(() => {
>
{{ phone.t('Apps.companies.requests.findCompany') }}
</k-button>
</div>
</k-block>
<k-list v-else inset strong class="company-request-list">
<k-list-item
v-for="item in companies.myRequests"
@@ -1359,26 +1308,38 @@ onBeforeUnmount(() => {
v-else-if="screen === 'root' && activeTab === 'work'"
class="companies-content companies-work"
>
<div v-if="companies.workContextLoading" class="companies-state">
<k-block
v-if="companies.workContextLoading"
inset
strong
class="companies-state"
>
<k-preloader />
<span>{{ phone.t('Apps.companies.loading.work') }}</span>
</div>
<div v-else-if="companies.workContextError" class="companies-state">
</k-block>
<k-block
v-else-if="companies.workContextError"
inset
strong
class="companies-state"
>
<CircleAlert :size="34" />
<strong>{{ phone.t('Apps.companies.states.workError') }}</strong>
<p>{{ errorText(companies.workContextError) }}</p>
<k-button rounded @click="selectTab('work')">
{{ phone.t('Apps.companies.tryAgain') }}
</k-button>
</div>
<div
</k-block>
<k-block
v-else-if="!companies.workContext?.authorized"
inset
strong
class="companies-state"
>
<BriefcaseBusiness :size="38" />
<strong>{{ phone.t('Apps.companies.work.notAuthorized') }}</strong>
<p>{{ phone.t('Apps.companies.work.notAuthorizedBody') }}</p>
</div>
</k-block>
<template v-else-if="companies.workContext && workCompany">
<k-card class="work-identity-card">
<span class="company-logo company-logo--large">
@@ -1454,98 +1415,25 @@ onBeforeUnmount(() => {
phone.t('Apps.companies.work.overview')
}}</k-block-title>
<div class="work-metrics">
<k-card>
<k-glass :highlight="false" class="work-metric">
<strong>{{ companies.workContext.metrics.new }}</strong>
<span>{{ phone.t('Apps.companies.work.metrics.new') }}</span>
</k-card>
<k-card>
</k-glass>
<k-glass :highlight="false" class="work-metric">
<strong>{{ companies.workContext.metrics.assigned }}</strong>
<span>{{ phone.t('Apps.companies.work.metrics.assigned') }}</span>
</k-card>
<k-card>
</k-glass>
<k-glass :highlight="false" class="work-metric">
<strong>{{ companies.workContext.metrics.waiting }}</strong>
<span>{{ phone.t('Apps.companies.work.metrics.waiting') }}</span>
</k-card>
<k-card>
</k-glass>
<k-glass :highlight="false" class="work-metric">
<strong>{{ companies.workContext.metrics.completedToday }}</strong>
<span>{{
phone.t('Apps.companies.work.metrics.completedToday')
}}</span>
</k-card>
</k-glass>
</div>
<k-block-title>{{
phone.t('Apps.companies.work.queue')
}}</k-block-title>
<div class="companies-chip-row work-filter-row">
<k-chip
v-for="filter in workFilters"
:key="filter"
component="button"
type="button"
:class="{ 'is-selected': workFilter === filter }"
@click="workFilter = filter"
>
{{ phone.t(`Apps.companies.work.filters.${filter}`) }}
</k-chip>
</div>
<div
v-if="companies.workQueueLoading"
class="companies-state companies-state--compact"
>
<k-preloader />
</div>
<div
v-else-if="companies.workQueueError"
class="companies-state companies-state--compact"
>
<CircleAlert :size="28" />
<p>{{ errorText(companies.workQueueError) }}</p>
<k-button small rounded @click="companies.loadWorkQueue(workFilter)">
{{ phone.t('Apps.companies.tryAgain') }}
</k-button>
</div>
<div
v-else-if="!companies.workQueue.length"
class="companies-state companies-state--compact"
>
<CheckCircle2 :size="31" />
<strong>{{ phone.t('Apps.companies.work.emptyQueue') }}</strong>
<p>{{ phone.t('Apps.companies.work.emptyQueueBody') }}</p>
</div>
<k-list v-else inset strong class="company-request-list">
<k-list-item
v-for="item in companies.workQueue"
:key="item.id"
link
link-component="button"
:link-props="{ type: 'button' }"
:title="item.subject"
:subtitle="requestSubtitle(item)"
:header="assignmentLabel(item.assignedLabel)"
@click="openRequest(item.id, 'work')"
>
<template #media
><span class="company-request-icon"
><BriefcaseBusiness :size="19" /></span
></template>
<template #after>
<k-badge :class="statusClass(item.status)">
{{ phone.t(`Apps.companies.requestStatuses.${item.status}`) }}
</k-badge>
</template>
</k-list-item>
</k-list>
<k-button
v-if="companies.workQueueNextCursor"
outline
rounded
class="companies-load-more"
:disabled="companies.workQueueLoadingMore"
@click="companies.loadWorkQueue(workFilter, true)"
>
{{ phone.t('Apps.companies.loadMore') }}
</k-button>
</template>
</section>
@@ -1553,15 +1441,19 @@ onBeforeUnmount(() => {
v-else-if="screen === 'company'"
class="companies-content company-profile"
>
<div
<k-block
v-if="companies.directoryLoading && !activeCompany"
inset
strong
class="companies-state"
>
<k-preloader />
<span>{{ phone.t('Apps.companies.loading.profile') }}</span>
</div>
<div
</k-block>
<k-block
v-else-if="companies.directoryError || !activeCompany"
inset
strong
class="companies-state"
>
<CircleAlert :size="34" />
@@ -1570,7 +1462,7 @@ onBeforeUnmount(() => {
<k-button rounded @click="goBack">{{
phone.t('Apps.companies.back')
}}</k-button>
</div>
</k-block>
<template v-else>
<div
class="company-cover"
@@ -1691,7 +1583,7 @@ onBeforeUnmount(() => {
/>
</k-list>
<div class="company-profile-actions">
<k-glass :highlight="false" class="company-profile-actions">
<k-button
rounded
:disabled="!activeCompany.canCall || !activeCompany.phoneNumber"
@@ -1726,7 +1618,7 @@ onBeforeUnmount(() => {
phone.t('Apps.companies.profile.request')
}}
</k-button>
</div>
</k-glass>
</template>
</section>
@@ -1734,12 +1626,19 @@ onBeforeUnmount(() => {
v-else-if="screen === 'request'"
class="companies-content request-thread"
>
<div v-if="companies.requestLoading" class="companies-state">
<k-block
v-if="companies.requestLoading"
inset
strong
class="companies-state"
>
<k-preloader />
<span>{{ phone.t('Apps.companies.loading.request') }}</span>
</div>
<div
</k-block>
<k-block
v-else-if="companies.requestError || !companies.request"
inset
strong
class="companies-state"
>
<CircleAlert :size="34" />
@@ -1748,7 +1647,7 @@ onBeforeUnmount(() => {
<k-button rounded @click="goBack">{{
phone.t('Apps.companies.back')
}}</k-button>
</div>
</k-block>
<template v-else>
<div class="request-thread-scroll">
<k-card class="request-summary-card">
@@ -1905,7 +1804,13 @@ onBeforeUnmount(() => {
phone.t('Apps.companies.manager.profile')
}}</k-block-title>
<div class="manager-media-grid">
<button type="button" @click="chooseManagerMedia('cover')">
<k-button
tonal
rounded
large
class="manager-media-button"
@click="chooseManagerMedia('cover')"
>
<img
v-if="managerCoverUrl"
:src="managerCoverUrl"
@@ -1914,8 +1819,14 @@ onBeforeUnmount(() => {
/>
<span v-else><ImagePlus :size="22" /></span>
<small>{{ phone.t('Apps.companies.manager.chooseCover') }}</small>
</button>
<button type="button" @click="chooseManagerMedia('logo')">
</k-button>
<k-button
tonal
rounded
large
class="manager-media-button"
@click="chooseManagerMedia('logo')"
>
<img
v-if="managerLogoUrl"
:src="managerLogoUrl"
@@ -1924,7 +1835,7 @@ onBeforeUnmount(() => {
/>
<span v-else><ImagePlus :size="22" /></span>
<small>{{ phone.t('Apps.companies.manager.chooseLogo') }}</small>
</button>
</k-button>
</div>
<k-list inset strong class="manager-form-list">
<k-list-input
@@ -2243,6 +2154,7 @@ onBeforeUnmount(() => {
>
<section
v-if="requestSheetOpened && activeCompany"
ref="requestSheetContent"
class="companies-sheet__content"
role="dialog"
aria-modal="true"
@@ -2264,10 +2176,10 @@ onBeforeUnmount(() => {
</k-link>
</header>
<k-progressbar :progress="requestProgress" />
<k-block-title>{{
<k-block-title class="composer-service-title">{{
phone.t('Apps.companies.composer.chooseService')
}}</k-block-title>
<k-list inset strong>
<k-list inset strong class="composer-service-list">
<k-list-item
v-for="service in activeCompany.services.filter(
(item) => item.active && item.acceptsRequests,
@@ -2276,7 +2188,11 @@ onBeforeUnmount(() => {
label
:title="service.title"
:subtitle="service.description"
:after="service.priceText ?? undefined"
title-font-size-ios="text-[15px]"
title-font-size-material="text-[15px]"
media-class="!py-2.5 !me-3"
inner-class="!py-2.5 !pe-safe-3"
title-wrap-class="!min-h-5"
>
<template #media>
<k-radio
@@ -2286,6 +2202,11 @@ onBeforeUnmount(() => {
@change="requestServiceId = service.id"
/>
</template>
<template v-if="service.priceText" #after>
<span class="composer-service-price">{{
service.priceText
}}</span>
</template>
</k-list-item>
</k-list>
<k-list inset strong class="composer-form-list">
@@ -2295,6 +2216,7 @@ onBeforeUnmount(() => {
:label="phone.t('Apps.companies.composer.subject')"
:placeholder="phone.t('Apps.companies.composer.subjectPlaceholder')"
:value="requestSubject"
:input-style="{ height: '38px' }"
@input="requestSubject = eventValue($event)"
/>
<k-list-input
@@ -2306,6 +2228,13 @@ onBeforeUnmount(() => {
phone.t('Apps.companies.composer.descriptionPlaceholder')
"
:value="requestDescription"
:input-style="{
height: '72px',
lineHeight: '20px',
paddingBottom: '8px',
paddingTop: '8px',
resize: 'none',
}"
@input="requestDescription = eventValue($event)"
/>
</k-list>
@@ -2356,10 +2285,11 @@ onBeforeUnmount(() => {
<k-button
large
rounded
:disabled="!canSubmitRequest"
@click="requestConfirmOpened = true"
:disabled="!canSubmitRequest || companies.mutating"
@click="submitRequest"
>
{{ phone.t('Apps.companies.composer.review') }}
<k-preloader v-if="companies.mutating" />
<span v-else>{{ phone.t('Apps.companies.composer.send') }}</span>
</k-button>
</section>
</k-sheet>
@@ -2433,12 +2363,14 @@ onBeforeUnmount(() => {
<X :size="19" />
</k-link>
</header>
<div
<k-block
v-if="companies.membersLoading"
inset
strong
class="companies-state companies-state--compact"
>
<k-preloader />
</div>
</k-block>
<k-list v-else inset strong>
<k-list-item
v-for="member in companies.members"
@@ -2472,31 +2404,6 @@ onBeforeUnmount(() => {
</section>
</k-sheet>
<k-dialog
:opened="requestConfirmOpened"
:title="phone.t('Apps.companies.composer.confirmTitle')"
:content="
phone.t('Apps.companies.composer.confirmBody', {
company: activeCompany?.name ?? '',
service: selectedService?.title ?? '',
})
"
@backdropclick="requestConfirmOpened = false"
>
<template #buttons>
<k-dialog-button @click="requestConfirmOpened = false">
{{ phone.t('Apps.companies.composer.edit') }}
</k-dialog-button>
<k-dialog-button
strong
:disabled="companies.mutating"
@click="submitRequest"
>
{{ phone.t('Apps.companies.composer.send') }}
</k-dialog-button>
</template>
</k-dialog>
<k-dialog
:opened="cancelDialogOpened"
:title="phone.t('Apps.companies.requests.cancelTitle')"
@@ -2554,6 +2461,8 @@ onBeforeUnmount(() => {
--k-safe-area-bottom: 25px;
position: relative;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
color: #111827;
}
@@ -2582,16 +2491,19 @@ onBeforeUnmount(() => {
.companies-content {
position: relative;
z-index: 1;
}
.companies-content {
height: calc(100% - 44px);
padding: 10px 10px 92px;
min-height: 0;
padding: 10px 10px calc(var(--k-safe-area-bottom) + 88px);
flex: 1;
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-width: none;
}
.companies-navbar {
flex: none;
}
.companies-content::-webkit-scrollbar,
.companies-chip-row::-webkit-scrollbar,
.companies-featured-row::-webkit-scrollbar {
@@ -2615,13 +2527,11 @@ onBeforeUnmount(() => {
white-space: nowrap;
}
.companies-chip-row :deep(.is-selected),
.companies-inline-chips :deep(.is-selected) {
.companies-chip-row :deep(.is-selected) {
color: white;
background: linear-gradient(135deg, var(--company-blue), var(--company-cyan));
}
.companies-filter-card,
.work-identity-card,
.manager-intro,
.request-summary-card,
@@ -2636,7 +2546,6 @@ onBeforeUnmount(() => {
-webkit-backdrop-filter: blur(18px) saturate(140%);
}
.companies-app--dark .companies-filter-card,
.companies-app--dark .work-identity-card,
.companies-app--dark .manager-intro,
.companies-app--dark .request-summary-card,
@@ -2646,20 +2555,9 @@ onBeforeUnmount(() => {
background: rgb(20 30 46 / 70%);
}
.companies-inline-chips {
display: flex;
max-width: 144px;
gap: 4px;
overflow-x: auto;
}
.companies-inline-chips :deep(.k-chip) {
flex: none;
font-size: 9px;
}
.companies-state {
min-height: 260px;
margin: 14px 6px;
padding: 36px 22px;
display: flex;
flex-direction: column;
@@ -2725,6 +2623,16 @@ onBeforeUnmount(() => {
margin-top: 6px;
}
.company-list :deep(.k-list-item > button),
.company-request-list :deep(.k-list-item > button) {
width: 100%;
text-align: left;
}
.companies-content :deep(.k-block-title) {
margin-bottom: 10px;
}
.company-logo {
width: 42px;
height: 42px;
@@ -2872,15 +2780,13 @@ onBeforeUnmount(() => {
margin: 0 5px 14px;
}
.work-metrics :deep(.k-card) {
.work-metric {
margin: 0;
border-radius: 16px;
}
.work-metrics :deep(> *) {
padding: 15px 16px;
display: flex;
flex-direction: column;
gap: 2px;
border-radius: 16px;
}
.work-metrics strong {
@@ -2893,10 +2799,6 @@ onBeforeUnmount(() => {
font-size: 10px;
}
.work-filter-row {
padding-inline: 5px;
}
.company-cover {
height: 145px;
margin: -10px -10px 0;
@@ -2918,10 +2820,6 @@ onBeforeUnmount(() => {
font-size: 18px;
}
.company-profile {
padding-bottom: 116px;
}
.company-profile-heading {
margin-top: 10px;
}
@@ -2956,7 +2854,17 @@ onBeforeUnmount(() => {
}
.company-profile {
padding-bottom: 145px;
padding-bottom: calc(var(--k-safe-area-bottom) + 24px);
}
.company-profile > :deep(.k-block-title) {
margin-top: 18px;
margin-bottom: 6px;
}
.company-profile > :deep(.k-list) {
margin-top: 8px;
margin-bottom: 10px;
}
.companies-app--dark .company-profile-heading p,
@@ -2973,26 +2881,19 @@ onBeforeUnmount(() => {
}
.company-profile-actions {
position: absolute;
position: relative;
z-index: 4;
right: 8px;
bottom: 28px;
left: 8px;
margin: 18px 0 0;
padding: 8px;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 7px;
border: 1px solid rgb(255 255 255 / 45%);
border-radius: 20px;
background: rgb(255 255 255 / 76%);
box-shadow: 0 18px 35px rgb(15 23 42 / 18%);
backdrop-filter: blur(22px) saturate(150%);
-webkit-backdrop-filter: blur(22px) saturate(150%);
}
.companies-app--dark .company-profile-actions {
border-color: rgb(255 255 255 / 8%);
background: rgb(15 23 42 / 80%);
}
.request-thread {
@@ -3149,7 +3050,7 @@ onBeforeUnmount(() => {
margin: 0 10px 12px;
}
.manager-media-grid button {
.manager-media-button {
min-width: 0;
min-height: 86px;
overflow: hidden;
@@ -3157,10 +3058,7 @@ onBeforeUnmount(() => {
display: grid;
place-items: center;
border: 1px solid rgb(148 163 184 / 30%);
border-radius: 16px;
padding: 0;
color: var(--company-blue);
background: rgb(255 255 255 / 62%);
}
.manager-media-grid img {
@@ -3185,10 +3083,6 @@ onBeforeUnmount(() => {
white-space: nowrap;
}
.companies-app--dark .manager-media-grid button {
background: rgb(30 41 59 / 66%);
}
.manager-location {
width: calc(100% - 20px);
margin: 0 10px 4px;
@@ -3274,8 +3168,47 @@ onBeforeUnmount(() => {
font-size: 9px;
}
.composer-service-title {
margin-top: 18px;
margin-bottom: 8px;
}
.composer-service-list {
margin-top: 0;
margin-right: 8px;
margin-bottom: 10px;
margin-left: 8px;
}
.composer-service-list :deep(.k-list-item .text-sm) {
overflow: hidden;
font-size: 11px;
line-height: 1.3;
text-overflow: ellipsis;
white-space: nowrap;
}
.composer-service-list :deep(.k-radio > i) {
width: 20px;
height: 20px;
}
.composer-service-price {
color: #64748b;
font-size: 11px;
font-weight: 500;
}
.composer-form-list {
margin-top: 12px;
margin-top: 10px;
margin-right: 8px;
margin-bottom: 10px;
margin-left: 8px;
}
.composer-form-list :deep(.k-list-input) {
margin-top: 8px;
margin-bottom: 8px;
}
.composer-contact-card {