Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show fields in data tab in Lead & Deal's page #447

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion crm/api/doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,9 @@ def get_fields_meta(doctype, restricted_fieldtypes=None, as_array=False):
fields_meta = {}
for field in fields:
fields_meta[field.get('fieldname')] = field

if field.get('fieldtype') == "Table":
_fields = frappe.get_meta(field.get('options')).fields
fields_meta[field.get('fieldname')] = {"df": field, "fields": _fields}
return fields_meta

@frappe.whitelist()
Expand Down
23 changes: 1 addition & 22 deletions crm/fcrm/doctype/crm_deal/api.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,12 @@
import frappe
from frappe import _

from crm.api.doc import get_fields_meta, get_assigned_users
from crm.fcrm.doctype.crm_form_script.crm_form_script import get_form_script

@frappe.whitelist()
def get_deal(name):
Deal = frappe.qb.DocType("CRM Deal")
deal = frappe.get_doc("CRM Deal", name).as_dict()

query = (
frappe.qb.from_(Deal)
.select("*")
.where(Deal.name == name)
.limit(1)
)

deal = query.run(as_dict=True)
if not len(deal):
frappe.throw(_("Deal not found"), frappe.DoesNotExistError)
deal = deal.pop()


deal["contacts"] = frappe.get_all(
"CRM Contacts",
filters={"parenttype": "CRM Deal", "parent": deal.name},
fields=["contact", "is_primary"],
)

deal["doctype"] = "CRM Deal"
deal["fields_meta"] = get_fields_meta("CRM Deal")
deal["_form_script"] = get_form_script('CRM Deal')
deal["_assign"] = get_assigned_users("CRM Deal", deal.name, deal.owner)
Expand Down
10 changes: 1 addition & 9 deletions crm/fcrm/doctype/crm_lead/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,8 @@

@frappe.whitelist()
def get_lead(name):
Lead = frappe.qb.DocType("CRM Lead")
lead = frappe.get_doc("CRM Lead", name).as_dict()

query = frappe.qb.from_(Lead).select("*").where(Lead.name == name).limit(1)

lead = query.run(as_dict=True)
if not len(lead):
frappe.throw(_("Lead not found"), frappe.DoesNotExistError)
lead = lead.pop()

lead["doctype"] = "CRM Lead"
lead["fields_meta"] = get_fields_meta("CRM Lead")
lead["_form_script"] = get_form_script('CRM Lead')
lead["_assign"] = get_assigned_users("CRM Lead", lead.name, lead.owner)
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/components/Activities/Activities.vue
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,9 @@
</div>
</div>
</div>
<div v-else-if="title == 'Data'" class="h-full flex flex-col px-3 sm:px-10">
<DocFields :doctype="doctype" :docname="doc.data.name" :meta="doc.data.fields_meta" />
</div>
<div
v-else
class="flex flex-1 flex-col items-center justify-center gap-3 text-xl font-medium text-gray-500"
Expand Down Expand Up @@ -449,6 +452,7 @@
/>
</template>
<script setup>
import DocFields from '@/components/DocFields.vue'
import ActivityHeader from '@/components/Activities/ActivityHeader.vue'
import EmailArea from '@/components/Activities/EmailArea.vue'
import CommentArea from '@/components/Activities/CommentArea.vue'
Expand All @@ -459,6 +463,7 @@ import AttachmentArea from '@/components/Activities/AttachmentArea.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import ActivityIcon from '@/components/Icons/ActivityIcon.vue'
import Email2Icon from '@/components/Icons/Email2Icon.vue'
import DetailsIcon from '@/components/Icons/DetailsIcon.vue'
import PhoneIcon from '@/components/Icons/PhoneIcon.vue'
import NoteIcon from '@/components/Icons/NoteIcon.vue'
import TaskIcon from '@/components/Icons/TaskIcon.vue'
Expand Down Expand Up @@ -727,6 +732,8 @@ const emptyText = computed(() => {
text = 'No Email Communications'
} else if (title.value == 'Comments') {
text = 'No Comments'
} else if (title.value == 'Data') {
text = 'No Data'
} else if (title.value == 'Calls') {
text = 'No Call Logs'
} else if (title.value == 'Notes') {
Expand All @@ -747,6 +754,8 @@ const emptyTextIcon = computed(() => {
icon = Email2Icon
} else if (title.value == 'Comments') {
icon = CommentIcon
} else if (title.value == 'Data') {
icon = DetailsIcon
} else if (title.value == 'Calls') {
icon = PhoneIcon
} else if (title.value == 'Notes') {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/Activities/ActivityHeader.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<div
v-if="title !== 'Data'"
class="mx-4 my-3 flex items-center justify-between text-lg font-medium sm:mx-10 sm:mb-4 sm:mt-8"
>
<div class="flex h-8 items-center text-xl font-semibold text-gray-800">
Expand Down
268 changes: 268 additions & 0 deletions frontend/src/components/Controls/Grid.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
<template>
<div class="flex flex-col text-base">
<div v-if="label" class="mb-1.5 text-sm text-gray-600">{{ label }}</div>

<div class="rounded border border-gray-100">
<!-- Header -->
<div
class="grid items-center rounded-t-sm bg-gray-100 text-gray-600"
:style="{ gridTemplateColumns: gridTemplateColumns }"
>
<div class="inline-flex items-center justify-center border-r p-2">
<Checkbox
class="cursor-pointer duration-300"
:modelValue="allRowsSelected"
@click.stop="toggleSelectAllRows($event.target.checked)"
/>
</div>
<div class="inline-flex items-center justify-center border-r py-2 px-1">
No.
</div>
<div
class="inline-flex items-center border-r p-2"
v-for="field in gridFields"
:key="field.fieldname"
>
{{ field.label }}
</div>
<div class="p-2" />
</div>
<!-- Rows -->
<template v-if="rows.length">
<Draggable class="w-full" v-model="rows" group="rows" item-key="name">
<template #item="{ element: row, index }">
<div
class="grid-row grid cursor-pointer items-center border-b border-gray-100 bg-white last:rounded-b last:border-b-0"
:style="{ gridTemplateColumns: gridTemplateColumns }"
>
<div
class="inline-flex h-full items-center justify-center border-r p-2"
>
<Checkbox
class="cursor-pointer duration-300"
:modelValue="selectedRows.has(row.name)"
@click.stop="toggleSelectRow(row)"
/>
</div>
<div
class="flex h-full items-center justify-center border-r p-2 text-sm text-gray-800"
>
{{ index + 1 }}
</div>
<div
class="border-r border-gray-100 h-full"
v-for="field in gridFields"
:key="field.fieldname"
>
<Link
v-if="field.fieldtype === 'Link'"
class="text-sm text-gray-800"
:value="row[field.fieldname]"
:doctype="field.options"
:placeholder="row.placeholder"
@change="
(data: String) =>
field.onChange && field.onChange(data, index)
"
/>
<div
v-else-if="field.fieldtype === 'Check'"
class="flex h-full justify-center items-center"
>
<Checkbox
class="cursor-pointer duration-300"
v-model="row[field.fieldname]"
@change="
(e: Event) =>
field.onChange &&
field.onChange(
(e.target as HTMLInputElement).checked,
index,
)
"
/>
</div>
<FormControl
v-else
class="text-sm text-gray-800"
v-model="row[field.fieldname]"
:type="field.fieldtype.toLowerCase()"
:options="field.options"
variant="outline"
size="md"
@change="
(e: Event) =>
field.onChange &&
field.onChange(
(e.target as HTMLInputElement).value,
index,
)
"
/>
</div>
<div class="edit-row">
<Button
class="flex w-full items-center justify-center rounded"
@click="showRowList[index] = true"
>
<FeatherIcon
name="edit-2"
class="h-3.5 w-3.5 text-gray-700"
/>
</Button>
</div>
<Dialog
v-model="showRowList[index]"
:options="{ title: `Editing Row ${index + 1}` }"
>
<template #body-content>
<div v-for="field in fields" :key="field.fieldname">
{{ field.label }}: {{ row[field.fieldname] }}
</div>
</template>
</Dialog>
</div>
</template>
</Draggable>
</template>

<div
v-else
class="flex flex-col items-center rounded p-5 text-sm text-gray-600"
>
No Data
</div>
</div>

<div class="mt-2 flex flex-row gap-2">
<Button
v-if="showDeleteBtn"
label="Delete"
variant="solid"
theme="red"
@click="deleteRows"
/>
<Button label="Add Row" @click="addRow" />
</div>
</div>
</template>

<script setup lang="ts">
import Link from '@/components/Controls/Link.vue'
import { GridColumn, GridRow } from '@/types/controls'
import { getRandom } from '@/utils'
import { Dialog, FormControl, Checkbox } from 'frappe-ui'
import Draggable from 'vuedraggable'
import { ref, reactive, computed, PropType } from 'vue'

const props = defineProps<{
label?: string
gridFields: GridColumn[]
fields: GridColumn[]
}>()

const rows = defineModel({
type: Array as PropType<GridRow[]>,
default: () => [],
})
const showRowList = ref(new Array(rows.value.length).fill(false))
const selectedRows = reactive(new Set<string>())

const gridTemplateColumns = computed(() => {
// for the checkbox & sr no. columns
let columns = '0.5fr 0.5fr'
columns +=
' ' +
props.gridFields.map((col) => `minmax(0, ${col.width || 2}fr)`).join(' ')
// for the edit button column
columns += ' 0.5fr'

return columns
})

const allRowsSelected = computed(() => {
if (!rows.value.length) return false
return rows.value.length === selectedRows.size
})

const showDeleteBtn = computed(() => selectedRows.size > 0)

const toggleSelectAllRows = (iSelected: boolean) => {
if (iSelected) {
rows.value.forEach((row: GridRow) => selectedRows.add(row.name))
} else {
selectedRows.clear()
}
}

const toggleSelectRow = (row: GridRow) => {
if (selectedRows.has(row.name)) {
selectedRows.delete(row.name)
} else {
selectedRows.add(row.name)
}
}

const addRow = () => {
const newRow = {} as GridRow
props.gridFields.forEach((field) => {
if (field.fieldtype === 'Check') newRow[field.fieldname] = false
else newRow[field.fieldname] = ''
})
newRow.name = getRandom(10)
showRowList.value.push(false)
newRow['__islocal'] = true
rows.value.push(newRow)
}

const deleteRows = () => {
rows.value = rows.value.filter((row) => !selectedRows.has(row.name))
showRowList.value.pop()
selectedRows.clear()
}
</script>

<style scoped>
/* For Input fields */
:deep(.grid-row input:not([type='checkbox'])) {
border: none;
border-radius: 0;
height: 38px;
}

:deep(.grid-row input:focus, .grid-row input:hover) {
box-shadow: none;
}

:deep(.grid-row input:focus-within) {
border: 1px solid #d1d8dd;
}

/* For select field */
:deep(.grid-row select) {
border: none;
border-radius: 0;
height: 38px;
}

/* For Autocomplete */
:deep(.grid-row button) {
border: none;
border-radius: 0;
background-color: white;
height: 38px;
}

:deep(.grid-row .edit-row button) {
border-bottom-right-radius: 7px;
}

:deep(.grid-row button:focus, .grid-row button:hover) {
box-shadow: none;
background-color: white;
}

:deep(.grid-row button:focus-within) {
border: 1px solid #d1d8dd;
}
</style>
Loading
Loading