feat: search in community page

This commit is contained in:
Jannat Patel
2022-01-06 17:10:07 +05:30
parent 2ec6a06204
commit e1e7354d85
14 changed files with 239 additions and 55 deletions

View File

@@ -30,7 +30,7 @@
"label": "Country",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 19:14:11.571754",
"modified": "2021-12-31 19:14:11.571754",
"module": null,
"name": "User-country",
"no_copy": 0,
@@ -84,7 +84,7 @@
"label": "Acceptance for Terms of Use",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 19:15:34.932908",
"modified": "2021-12-31 19:15:34.932908",
"module": null,
"name": "User-verify_terms",
"no_copy": 0,
@@ -138,7 +138,7 @@
"label": "City",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 14:46:55.834145",
"modified": "2021-12-31 14:46:55.834145",
"module": null,
"name": "User-city",
"no_copy": 0,
@@ -192,7 +192,7 @@
"label": "College Name",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 14:46:55.834145",
"modified": "2021-12-31 14:46:55.834145",
"module": null,
"name": "User-college",
"no_copy": 0,
@@ -246,7 +246,7 @@
"label": "Branch",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 14:46:55.834145",
"modified": "2021-12-31 14:46:55.834145",
"module": null,
"name": "User-branch",
"no_copy": 0,
@@ -300,7 +300,7 @@
"label": "LinkedIn ID",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 14:46:55.834145",
"modified": "2021-12-31 14:46:55.834145",
"module": null,
"name": "User-linkedin",
"no_copy": 0,
@@ -354,7 +354,7 @@
"label": "Github ID",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 14:46:55.834145",
"modified": "2021-12-31 14:46:55.834145",
"module": null,
"name": "User-github",
"no_copy": 0,
@@ -408,7 +408,7 @@
"label": "Medium ID",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 14:46:55.834145",
"modified": "2021-12-31 14:46:55.834145",
"module": null,
"name": "User-medium",
"no_copy": 0,
@@ -462,7 +462,7 @@
"label": "Profession",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 14:46:55.834145",
"modified": "2021-12-31 14:46:55.834145",
"module": null,
"name": "User-profession",
"no_copy": 0,
@@ -516,7 +516,7 @@
"label": "Education Details",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 11:57:55.170625",
"modified": "2021-12-31 11:57:55.170625",
"module": null,
"name": "User-education_details",
"no_copy": 0,
@@ -570,7 +570,7 @@
"label": "Hide my Private Information from others",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 11:57:47.942967",
"modified": "2021-12-31 11:57:47.942967",
"module": null,
"name": "User-hide_my_private_information_from_others",
"no_copy": 0,
@@ -624,7 +624,7 @@
"label": "Profile Complete",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 11:00:13.792809",
"modified": "2021-12-31 11:00:13.792809",
"module": null,
"name": "User-profile_complete",
"no_copy": 0,
@@ -678,7 +678,7 @@
"label": "Cover Image",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 10:59:52.682112",
"modified": "2021-12-31 10:59:52.682112",
"module": null,
"name": "User-cover_image",
"no_copy": 0,
@@ -732,7 +732,7 @@
"label": "I am looking for a job",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-28 12:56:32.110403",
"modified": "2021-12-31 12:56:32.110403",
"module": null,
"name": "User-looking_for_job",
"no_copy": 0,
@@ -786,7 +786,7 @@
"label": "Education",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 11:58:56.052663",
"modified": "2021-12-31 11:58:56.052663",
"module": null,
"name": "User-education",
"no_copy": 0,
@@ -840,7 +840,7 @@
"label": "Work Experience Details",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 11:56:29.466560",
"modified": "2021-12-31 11:56:29.466560",
"module": null,
"name": "User-work_experience_details",
"no_copy": 0,
@@ -894,7 +894,7 @@
"label": "Work Experience",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 11:58:43.329371",
"modified": "2021-12-31 11:58:43.329371",
"module": null,
"name": "User-work_experience",
"no_copy": 0,
@@ -948,7 +948,7 @@
"label": "Volunteering or Internship",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 14:58:04.285835",
"modified": "2021-12-31 14:58:04.285835",
"module": null,
"name": "User-internship",
"no_copy": 0,
@@ -998,11 +998,11 @@
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "volunteering_or_internship",
"insert_after": "internship",
"label": "Certification Details",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 14:58:29.975380",
"modified": "2021-12-31 14:58:29.975380",
"module": null,
"name": "User-certification_details",
"no_copy": 0,
@@ -1056,7 +1056,7 @@
"label": "Certification",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 11:59:54.850517",
"modified": "2021-12-31 11:59:54.850517",
"module": null,
"name": "User-certification",
"no_copy": 0,
@@ -1110,7 +1110,7 @@
"label": "Skill Details",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 12:01:29.335323",
"modified": "2021-12-31 12:01:29.335323",
"module": null,
"name": "User-skill_details",
"no_copy": 0,
@@ -1164,7 +1164,7 @@
"label": "Skill",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 15:32:13.944672",
"modified": "2021-12-31 15:32:13.944672",
"module": null,
"name": "User-skill",
"no_copy": 0,
@@ -1218,7 +1218,7 @@
"label": "Career Preference Details",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 15:35:50.764368",
"modified": "2021-12-31 15:35:50.764368",
"module": null,
"name": "User-carrer_preference_details",
"no_copy": 0,
@@ -1272,7 +1272,7 @@
"label": "Preferred Functions",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 17:12:36.454519",
"modified": "2021-12-31 17:12:36.454519",
"module": null,
"name": "User-preferred_functions",
"no_copy": 0,
@@ -1326,7 +1326,7 @@
"label": "Preferred Location",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 17:12:40.105066",
"modified": "2021-12-31 17:12:40.105066",
"module": null,
"name": "User-preferred_location",
"no_copy": 0,
@@ -1380,7 +1380,7 @@
"label": "",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 12:47:32.673594",
"modified": "2021-12-31 12:47:32.673594",
"module": null,
"name": "User-career_preference_column",
"no_copy": 0,
@@ -1434,7 +1434,7 @@
"label": "Preferred Industries",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 17:12:31.604282",
"modified": "2021-12-31 17:12:31.604282",
"module": null,
"name": "User-preferred_industries",
"no_copy": 0,
@@ -1488,7 +1488,7 @@
"label": "Dream Companies",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 12:49:19.295124",
"modified": "2021-12-31 12:49:19.295124",
"module": null,
"name": "User-dream_companies",
"no_copy": 0,
@@ -1542,7 +1542,7 @@
"label": "Work Environment",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 12:49:46.685634",
"modified": "2021-12-31 12:49:46.685634",
"module": null,
"name": "User-work_environment",
"no_copy": 0,
@@ -1596,7 +1596,7 @@
"label": "Attire Preference",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 12:03:02.296214",
"modified": "2021-12-31 12:03:02.296214",
"module": null,
"name": "User-attire",
"no_copy": 0,
@@ -1650,7 +1650,7 @@
"label": "Collaboration Preference",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 12:02:49.680208",
"modified": "2021-12-31 12:02:49.680208",
"module": null,
"name": "User-collaboration",
"no_copy": 0,
@@ -1704,7 +1704,7 @@
"label": "Role Preference",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 16:10:37.349479",
"modified": "2021-12-31 16:10:37.349479",
"module": null,
"name": "User-role",
"no_copy": 0,
@@ -1758,7 +1758,7 @@
"label": "",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 16:45:46.776903",
"modified": "2021-12-31 16:45:46.776903",
"module": null,
"name": "User-work_environment_column",
"no_copy": 0,
@@ -1812,7 +1812,7 @@
"label": "Location Preference",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 12:02:04.328536",
"modified": "2021-12-31 12:02:04.328536",
"module": null,
"name": "User-location_preference",
"no_copy": 0,
@@ -1866,7 +1866,7 @@
"label": "Time Preference",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 16:16:37.885306",
"modified": "2021-12-31 16:16:37.885306",
"module": null,
"name": "User-time",
"no_copy": 0,
@@ -1920,7 +1920,7 @@
"label": "Company Type",
"length": 0,
"mandatory_depends_on": null,
"modified": "2021-12-22 12:01:41.342622",
"modified": "2021-12-31 12:01:41.342622",
"module": null,
"name": "User-company_type",
"no_copy": 0,
@@ -1943,4 +1943,4 @@
"unique": 0,
"width": null
}
]
]

View File

@@ -1,5 +1,5 @@
<div class="common-card-style member-card {{dimension_class}} ">
{% set avatar_class = "avatar-large" if not dimension_class else "avatar-large"%}
{% set avatar_class = "avatar-xl" if not dimension_class else "avatar-large"%}
{{ widgets.Avatar(member=member, avatar_class=avatar_class) }}
<div class="small-title member-card-title">
{{ member.full_name }}

View File

@@ -1,6 +1,6 @@
import frappe
from frappe.core.doctype.user.user import User
from frappe.utils import cint, escape_html, random_string
from frappe.utils import cint, escape_html, random_string, unique
import hashlib
import random
import re
@@ -8,6 +8,7 @@ from frappe import _
from frappe.website.utils import is_signup_disabled
import requests
from frappe.geo.country_info import get_all
from school.widgets import Widgets
class CustomUser(User):
@@ -237,3 +238,72 @@ def get_country_code():
pass
return {}
@frappe.whitelist(allow_guest=True)
def search_users(start=0, text=""):
or_filters = get_or_filters(text)
count = len(get_users(or_filters, 0, 900000000, text))
users = get_users(or_filters, start, 30, text)
user_details = get_user_details(users)
return {
"user_details": user_details,
"start": cint(start) + 30,
"count": count
}
def get_or_filters(text):
user_fields = ["first_name", "last_name", "full_name", "email", "preferred_location", "dream_companies"]
education_fields = ["institution_name", "location", "degree_type", "major"]
work_fields = ["title", "company"]
certification_fields = ["certification_name", "organization"]
or_filters = []
if text:
for field in user_fields:
or_filters.append(f"u.{field} like '%{text}%'")
for field in education_fields:
or_filters.append(f"ed.{field} like '%{text}%'")
for field in work_fields:
or_filters.append(f"we.{field} like '%{text}%'")
for field in certification_fields:
or_filters.append(f"c.{field} like '%{text}%'")
or_filters.append(f"s.skill_name like '%{text}%'")
or_filters.append(f"pf.function like '%{text}%'")
or_filters.append(f"pi.industry like '%{text}%'")
return "AND {}".format(" OR ".join(or_filters)) if or_filters else ""
def get_user_details(users):
user_details = []
for user in users:
details = frappe.get_doc("User", user)
user_details.append(Widgets().MemberCard(member=details))
return user_details
def get_users(or_filters, start, page_length, text):
users = frappe.db.sql("""
SELECT DISTINCT u.name
FROM `tabUser` u
LEFT JOIN `tabEducation Detail` ed
ON u.name = ed.parent
LEFT JOIN `tabWork Experience` we
ON u.name = we.parent
LEFT JOIN `tabCertification` c
ON u.name = c.parent
LEFT JOIN `tabSkills` s
ON u.name = s.parent
LEFT JOIN `tabPreferred Function` pf
ON u.name = pf.parent
LEFT JOIN `tabPreferred Industry` pi
ON u.name = pi.parent
WHERE u.enabled = True {or_filters}
ORDER BY u.creation desc
LIMIT {start}, {page_length}
""".format(or_filters = or_filters, start=start, page_length=page_length), as_dict=1)
return users

View File

@@ -546,7 +546,7 @@ input[type=checkbox] {
.profile-parent-section {
display: grid;
grid-gap: 2rem;
grid-template-columns: 4fr 1fr;
grid-template-columns: 5fr 1.5fr;
}
@media (max-width: 768px) {
@@ -645,7 +645,7 @@ input[type=checkbox] {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 0px 16px;
padding: 1rem 0;
}
.member-card .talk-title {
@@ -670,7 +670,8 @@ input[type=checkbox] {
}
.member-card-title {
margin: 12px 0px 4px;
margin-top: 0.75rem;
text-align: center;
}
.member-card-large .member-card-title {
@@ -740,6 +741,11 @@ input[type=checkbox] {
height: 88px;
}
.member-card .avatar-xl {
width: 150px;
height: 150px;
}
.avatar-xl {
width: 112px;
height: 112px;
@@ -799,16 +805,16 @@ input[type=checkbox] {
}
}
.mentors-section {
.member-parent {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
-moz-column-gap: 32px;
column-gap: 32px;
row-gap: 32px;
}
@media (max-width: 600px) {
.mentors-section {
.member-parent {
grid-template-columns: repeat(auto-fill, minmax(125px, 1fr));
-moz-column-gap: 24px;
column-gap: 24px;
@@ -1318,7 +1324,7 @@ pre {
margin-left: 1.5rem;
}
.search-course {
.search {
background-image: url(/assets/frappe/icons/timeless/search.svg);
border: 1px solid #C8CFD5;
box-sizing: border-box;

View File

@@ -2,7 +2,7 @@
{% set search_placeholder = frappe.db.get_single_value("LMS Settings", "search_placeholder") %}
{% if show_search %}
<input class="search-course" placeholder="{{ _(search_placeholder) }}">
<input class="search" id="search-course" placeholder="{{ _(search_placeholder) }}">
<div id="" class="alert alert-dismissible empty-state search-empty-state hide">
<a href="#" class="close-search-empty-state" aria-label="close">&times;</a>

View File

@@ -1,5 +1,5 @@
frappe.ready(() => {
$(".search-course").keyup((e) => {
$("#search-course").keyup((e) => {
search_course(e);
});
@@ -70,5 +70,5 @@ const fix_heading_styles = () => {
const close_search_empty_state = (e) => {
$(".search-empty-state").addClass("hide");
$(".search-course").val("");
$("#search-course").val("");
}

View File

@@ -54,7 +54,7 @@
<h5>Mentors</h5>
{% set mentors = subgroup.get_mentors() %}
{% if mentors %}
<div class="mentors-section">
<div class="member-parent">
{% for m in mentors %}
{{ widgets.MemberCard(member=m, show_course_count=False, dimension_class="") }}
{% endfor %}
@@ -68,7 +68,7 @@
{% macro render_students() %}
{% set students = subgroup.get_students() %}
{% if students %}
<div class="mentors-section">
<div class="member-parent">
{% for student in students %}
{{ widgets.MemberCard(member=student, show_course_count=False, dimension_class="") }}
{% endfor %}

View File

View File

@@ -0,0 +1,32 @@
{% extends "templates/base.html" %}
{% from "www/hackathons/macros/card.html" import null_card %}
{% block title %}{{ _('Community') }}{% endblock %}
{% block content %}
<div class="common-page-style">
<div class="container">
<input class="search" id="search-user" placeholder="{{ _('Try a Name, Company, or Industry') }}">
<div class="course-home-headings">Community</div>
<div class="alert alert-dismissible empty-state text-center hide" id="search-empty-state">
<a href="#" class="close-search-empty-state" aria-label="close">&times;</a>
<img class="icon icon-xl" src="/assets/frappe/images/ui-states/search-empty-state.svg">
<div class="course-home-headings mt-4 mb-0" style="color: inherit;"> {{ _("No results found") }} </div>
<div class="small mb-6"> {{ _("Try some other keyword or explore our community") }} </div>
</div>
<div class="member-parent">
{% for user in user_details %}
{{ widgets.MemberCard(member=user, show_course_count=False) }}
{% endfor %}
</div>
{% if user_count > user_details | length %}
<div class="mt-10 d-flex justify-content-center">
<div class="button is-secondary" id="load-more" data-start="30" data-count="{{ user_count }}">Load More</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,58 @@
frappe.ready(() => {
$("#load-more").click((e) => {
search(e);
});
$(".close-search-empty-state").click((e) => {
close_search_empty_state(e);
});
$("#search-user").keyup(function() {
let timer;
clearTimeout(timer);
timer = setTimeout(() => { search.apply(this, arguments); }, 300);
});
});
const search = (e) => {
$("#search-empty-state").addClass("hide");
let start = $(e.currentTarget).data("start")
let input = $("#search-user").val();
if ($(e.currentTarget).prop("nodeName") == "INPUT")
start = 0;
frappe.call({
method: "school.overrides.user.search_users",
args: {
"start": start,
"text": input
},
callback: (data) => {
if ($(e.currentTarget).prop("nodeName") == "INPUT")
$(".member-parent").empty();
if (data.message.user_details.length)
$("#load-more").removeClass("hide");
else
$("#search-empty-state").removeClass("hide");
$(".member-parent").append(data.message.user_details);
update_load_more_state(data);
}
});
}
const close_search_empty_state = (e) => {
$("#search-empty-state").addClass("hide");
$("#search-user").val("").keyup();
}
const update_load_more_state = (data) => {
$("#load-more").data("start", data.message.start);
$("#load-more").data("count", data.message.count);
if ($(".member-card").length == $("#load-more").data("count")) {
$("#load-more").addClass("hide");
}
}

View File

@@ -0,0 +1,17 @@
import frappe
def get_context(context):
context.user_count = frappe.db.count("User", {"enabled": True})
users = frappe.get_all("User",
{"enabled": True},
pluck="name",
start=0,
page_length=30,
order_by="creation desc")
user_details = []
for user in users:
details = frappe.get_doc("User", user)
user_details.append(details)
context.user_details = user_details

View File

@@ -216,7 +216,7 @@
<div class="course-home-headings">
Mentors
</div>
<div class="mentors-section">
<div class="member-parent">
{% for mentor in course.get_mentors() %}
{{ widgets.MemberCard(member=mentor, show_course_count=False, dimension_class="") }}
{% endfor %}

View File

@@ -61,7 +61,7 @@ var check_mentor_request = () => {
var hide_wrapped_mentor_cards = () => {
var offset_top_prev;
$(".mentors-section .member-card").each(function () {
$(".member-parent .member-card").each(function () {
var offset_top = $(this).offset().top;
if (offset_top > offset_top_prev) {
$(this).addClass('wrapped').slideUp("fast");

View File

@@ -254,8 +254,9 @@
{% endif %}
{% if member.linkedin %}
{% set linkedin = member.linkedin[:-1] if member.linkedin[-1] == "/" else member.linkedin %}
<a class="button-links" href="{{ member.linkedin }}">
<img src="/assets/school/icons/linkedin.svg"> {{ member.linkedin.split("/")[-1] }}
<img src="/assets/school/icons/linkedin.svg"> {{ linkedin.split("/")[-1] }}
</a>
{% endif %}
{% if member.medium %}