1
0
forked from test/crm

fix: updated organization page design based on new design

This commit is contained in:
Shariq Ansari 2024-10-30 13:13:42 +05:30
parent 9461e0185b
commit db1e6ed7f4

View File

@ -8,169 +8,130 @@
</Breadcrumbs> </Breadcrumbs>
</template> </template>
</LayoutHeader> </LayoutHeader>
<div v-if="organization.doc" class="flex flex-1 flex-col overflow-hidden"> <div ref="parentRef" class="flex h-full">
<FileUploader <Resizer
@success="changeOrganizationImage" v-if="organization.doc"
:validateFile="validateFile" :parent="$refs.parentRef"
class="flex h-full flex-col overflow-hidden border-r"
> >
<template #default="{ openFileSelector, error }"> <div class="border-b">
<div class="flex items-start justify-start gap-6 p-5 sm:items-center"> <FileUploader
<div class="group relative h-24 w-24"> @success="changeOrganizationImage"
<Avatar :validateFile="validateFile"
size="3xl" >
:image="organization.doc.organization_logo" <template #default="{ openFileSelector, error }">
:label="organization.doc.name" <div class="flex flex-col items-start justify-start gap-4 p-5">
class="!h-24 !w-24" <div class="flex gap-4 items-center">
/> <div class="group relative h-15.5 w-15.5">
<component <Avatar
:is="organization.doc.organization_logo ? Dropdown : 'div'" size="3xl"
v-bind=" class="h-15.5 w-15.5"
organization.doc.organization_logo :label="organization.doc.organization_name"
? { :image="organization.doc.organization_logo"
options: [ />
{ <component
icon: 'upload', :is="organization.doc.image ? Dropdown : 'div'"
label: organization.doc.organization_logo v-bind="
? __('Change image') organization.doc.image
: __('Upload image'), ? {
onClick: openFileSelector, options: [
}, {
{ icon: 'upload',
icon: 'trash-2', label: organization.doc.image
label: __('Remove image'), ? __('Change image')
onClick: () => changeOrganizationImage(''), : __('Upload image'),
}, onClick: openFileSelector,
], },
} {
: { onClick: openFileSelector } icon: 'trash-2',
" label: __('Remove image'),
class="!absolute bottom-0 left-0 right-0" onClick: () => changeOrganizationImage(''),
> },
<div ],
class="z-1 absolute bottom-0 left-0 right-0 flex h-13 cursor-pointer items-center justify-center rounded-b-full bg-black bg-opacity-40 pt-3 opacity-0 duration-300 ease-in-out group-hover:opacity-100" }
style=" : { onClick: openFileSelector }
-webkit-clip-path: inset(12px 0 0 0); "
clip-path: inset(12px 0 0 0); class="!absolute bottom-0 left-0 right-0"
" >
> <div
<CameraIcon class="h-6 w-6 cursor-pointer text-white" /> class="z-1 absolute bottom-0 left-0 right-0 flex h-14 cursor-pointer items-center justify-center rounded-b-full bg-black bg-opacity-40 pt-5 opacity-0 duration-300 ease-in-out group-hover:opacity-100"
style="
-webkit-clip-path: inset(22px 0 0 0);
clip-path: inset(22px 0 0 0);
"
>
<CameraIcon class="h-6 w-6 cursor-pointer text-white" />
</div>
</component>
</div>
<div class="flex flex-col gap-2 truncate">
<div class="truncate text-2xl font-medium">
<span>{{ organization.doc.name }}</span>
</div>
<div
v-if="organization.doc.website"
class="flex items-center gap-1.5 text-base text-gray-800"
>
<WebsiteIcon class="size-4" />
<span>{{ website(organization.doc.website) }}</span>
</div>
<ErrorMessage :message="__(error)" />
</div>
</div>
<div class="flex gap-1.5">
<Button
:label="__('Delete')"
theme="red"
size="sm"
@click="deleteOrganization"
>
<template #prefix>
<FeatherIcon name="trash-2" class="h-4 w-4" />
</template>
</Button>
<Button size="sm" @click="openWebsite">
<FeatherIcon name="link" class="h-4 w-4" />
</Button>
</div> </div>
</component>
</div>
<div class="flex flex-col justify-center gap-2 sm:gap-0.5">
<div class="text-3xl font-semibold text-gray-900">
{{ organization.doc.name }}
</div> </div>
<div </template>
class="flex flex-col flex-wrap gap-3 text-base text-gray-700 sm:flex-row sm:items-center sm:gap-2" </FileUploader>
> </div>
<div <div
v-if="organization.doc.website" v-if="fieldsLayout.data"
class="flex items-center gap-1.5" class="flex flex-1 flex-col justify-between overflow-hidden"
> >
<WebsiteIcon class="h-4 w-4" /> <div class="flex flex-col overflow-y-auto">
<span class="">{{ website(organization.doc.website) }}</span> <div
</div> v-for="(section, i) in fieldsLayout.data"
<span :key="section.label"
v-if="organization.doc.website" class="flex flex-col p-3"
class="hidden text-3xl leading-[0] text-gray-600 sm:flex" :class="{ 'border-b': i !== fieldsLayout.data.length - 1 }"
> >
&middot; <Section :is-opened="section.opened" :label="section.label">
</span> <template #actions>
<div <Button
v-if="organization.doc.industry" v-if="i == 0 && isManager()"
class="flex items-center gap-1.5" variant="ghost"
> class="w-7"
<FeatherIcon name="briefcase" class="h-4 w-4" /> @click="showSidePanelModal = true"
<span class="">{{ organization.doc.industry }}</span> >
</div>
<span
v-if="organization.doc.industry"
class="hidden text-3xl leading-[0] text-gray-600 sm:flex"
>
&middot;
</span>
<div
v-if="organization.doc.territory"
class="flex items-center gap-1.5"
>
<TerritoryIcon class="h-4 w-4" />
<span class="">{{ organization.doc.territory }}</span>
</div>
<span
v-if="organization.doc.territory"
class="hidden text-3xl leading-[0] text-gray-600 sm:flex"
>
&middot;
</span>
<div
v-if="organization.doc.annual_revenue"
class="flex items-center gap-1.5"
>
<MoneyIcon class="size-4" />
<span class="">{{
formatNumberIntoCurrency(
organization.doc.annual_revenue,
organization.doc.currency,
)
}}</span>
</div>
<span
v-if="organization.doc.annual_revenue"
class="hidden text-3xl leading-[0] text-gray-600 sm:flex"
>
&middot;
</span>
<Button
v-if="
organization.doc.website ||
organization.doc.industry ||
organization.doc.territory ||
organization.doc.annual_revenue
"
variant="ghost"
:label="__('More')"
class="w-fit cursor-pointer hover:text-gray-900 sm:-ml-1"
@click="
() => {
detailMode = true
showOrganizationModal = true
}
"
/>
</div>
<div class="mt-2 flex gap-1.5">
<Button
:label="__('Edit')"
size="sm"
@click="
() => {
detailMode = false
showOrganizationModal = true
}
"
>
<template #prefix>
<EditIcon class="h-4 w-4" /> <EditIcon class="h-4 w-4" />
</template> </Button>
</Button> </template>
<Button <SectionFields
:label="__('Delete')" v-if="section.fields"
theme="red" :fields="section.fields"
size="sm" :isLastSection="i == fieldsLayout.data.length - 1"
@click="deleteOrganization" v-model="organization.doc"
> @update="updateField"
<template #prefix> />
<FeatherIcon name="trash-2" class="h-4 w-4" /> </Section>
</template>
</Button>
</div>
<ErrorMessage class="mt-2" :message="__(error)" />
</div> </div>
</div> </div>
</template> </div>
</FileUploader> </Resizer>
<Tabs v-model="tabIndex" :tabs="tabs"> <Tabs class="overflow-hidden" v-model="tabIndex" :tabs="tabs">
<template #tab="{ tab, selected }"> <template #tab="{ tab, selected }">
<button <button
class="group flex items-center gap-2 border-b border-transparent py-2.5 text-base text-gray-600 duration-300 ease-in-out hover:border-gray-400 hover:text-gray-900" class="group flex items-center gap-2 border-b border-transparent py-2.5 text-base text-gray-600 duration-300 ease-in-out hover:border-gray-400 hover:text-gray-900"
@ -216,29 +177,32 @@
</template> </template>
</Tabs> </Tabs>
</div> </div>
<OrganizationModal <SidePanelModal
v-model="showOrganizationModal" v-if="showSidePanelModal"
v-model:quickEntry="showQuickEntryModal" v-model="showSidePanelModal"
v-model:organization="organization" doctype="CRM Organization"
:options="{ detailMode }" @reload="() => fieldsLayout.reload()"
/> />
<QuickEntryModal <QuickEntryModal
v-if="showQuickEntryModal" v-if="showQuickEntryModal"
v-model="showQuickEntryModal" v-model="showQuickEntryModal"
doctype="CRM Organization" doctype="CRM Organization"
/> />
<AddressModal v-model="showAddressModal" v-model:address="_address" />
</template> </template>
<script setup> <script setup>
import Resizer from '@/components/Resizer.vue'
import Section from '@/components/Section.vue'
import SectionFields from '@/components/SectionFields.vue'
import SidePanelModal from '@/components/Settings/SidePanelModal.vue'
import Icon from '@/components/Icon.vue' import Icon from '@/components/Icon.vue'
import LayoutHeader from '@/components/LayoutHeader.vue' import LayoutHeader from '@/components/LayoutHeader.vue'
import OrganizationModal from '@/components/Modals/OrganizationModal.vue'
import QuickEntryModal from '@/components/Modals/QuickEntryModal.vue' import QuickEntryModal from '@/components/Modals/QuickEntryModal.vue'
import AddressModal from '@/components/Modals/AddressModal.vue'
import DealsListView from '@/components/ListViews/DealsListView.vue' import DealsListView from '@/components/ListViews/DealsListView.vue'
import ContactsListView from '@/components/ListViews/ContactsListView.vue' import ContactsListView from '@/components/ListViews/ContactsListView.vue'
import WebsiteIcon from '@/components/Icons/WebsiteIcon.vue' import WebsiteIcon from '@/components/Icons/WebsiteIcon.vue'
import TerritoryIcon from '@/components/Icons/TerritoryIcon.vue'
import MoneyIcon from '@/components/Icons/MoneyIcon.vue'
import EditIcon from '@/components/Icons/EditIcon.vue' import EditIcon from '@/components/Icons/EditIcon.vue'
import CameraIcon from '@/components/Icons/CameraIcon.vue' import CameraIcon from '@/components/Icons/CameraIcon.vue'
import DealsIcon from '@/components/Icons/DealsIcon.vue' import DealsIcon from '@/components/Icons/DealsIcon.vue'
@ -252,6 +216,7 @@ import {
dateTooltipFormat, dateTooltipFormat,
timeAgo, timeAgo,
formatNumberIntoCurrency, formatNumberIntoCurrency,
createToast,
} from '@/utils' } from '@/utils'
import { import {
Breadcrumbs, Breadcrumbs,
@ -263,6 +228,7 @@ import {
createListResource, createListResource,
createDocumentResource, createDocumentResource,
usePageMeta, usePageMeta,
createResource,
} from 'frappe-ui' } from 'frappe-ui'
import { h, computed, ref } from 'vue' import { h, computed, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
@ -274,11 +240,11 @@ const props = defineProps({
}, },
}) })
const { getUser, isManager } = usersStore()
const { $dialog } = globalStore() const { $dialog } = globalStore()
const { getDealStatus } = statusesStore() const { getDealStatus } = statusesStore()
const showOrganizationModal = ref(false) const showSidePanelModal = ref(false)
const showQuickEntryModal = ref(false) const showQuickEntryModal = ref(false)
const detailMode = ref(false)
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -291,6 +257,17 @@ const organization = createDocumentResource({
auto: true, auto: true,
}) })
async function updateField(fieldname, value) {
await organization.setValue.submit({
[fieldname]: value,
})
createToast({
title: __('Organization updated'),
icon: 'check',
iconClasses: 'text-green-600',
})
}
const breadcrumbs = computed(() => { const breadcrumbs = computed(() => {
let items = [{ label: __('Organizations'), route: { name: 'Organizations' } }] let items = [{ label: __('Organizations'), route: { name: 'Organizations' } }]
@ -372,6 +349,60 @@ function website(url) {
return url && url.replace(/^(?:https?:\/\/)?(?:www\.)?/i, '') return url && url.replace(/^(?:https?:\/\/)?(?:www\.)?/i, '')
} }
function openWebsite() {
if (!organization.doc.website)
createToast({
title: __('Website not found'),
icon: 'x',
iconClasses: 'text-red-600',
})
else window.open(organization.doc.website, '_blank')
}
const showAddressModal = ref(false)
const _organization = ref({})
const _address = ref({})
const fieldsLayout = createResource({
url: 'crm.api.doc.get_sidebar_fields',
cache: ['fieldsLayout', props.organizationId],
params: { doctype: 'CRM Organization', name: props.organizationId },
auto: true,
transform: (data) => getParsedFields(data),
})
function getParsedFields(data) {
return data.map((section) => {
return {
...section,
fields: computed(() =>
section.fields.map((field) => {
if (field.name === 'address') {
return {
...field,
create: (value, close) => {
_organization.value.address = value
_address.value = {}
showAddressModal.value = true
close()
},
edit: async (addr) => {
_address.value = await call('frappe.client.get', {
doctype: 'Address',
name: addr,
})
showAddressModal.value = true
},
}
} else {
return field
}
}),
),
}
})
}
const tabIndex = ref(0) const tabIndex = ref(0)
const tabs = [ const tabs = [
{ {
@ -386,8 +417,6 @@ const tabs = [
}, },
] ]
const { getUser } = usersStore()
const deals = createListResource({ const deals = createListResource({
type: 'list', type: 'list',
doctype: 'CRM Deal', doctype: 'CRM Deal',