mirror of
https://github.com/skoelle/icloud-contacts-sync.git
synced 2026-09-17 15:30:24 +00:00
web group cloud
This commit is contained in:
+35
-17
@@ -326,6 +326,7 @@ def web_dashboard(
|
|||||||
with db.get_connection() as conn:
|
with db.get_connection() as conn:
|
||||||
contact_count = db.get_contact_count(conn, account_name)
|
contact_count = db.get_contact_count(conn, account_name)
|
||||||
upcoming_birthdays = db.get_upcoming_birthdays(conn, account_name, 7)
|
upcoming_birthdays = db.get_upcoming_birthdays(conn, account_name, 7)
|
||||||
|
groups = db.get_all_groups(conn, account_name)
|
||||||
where_clause, params = _account_filter_clause(account_name)
|
where_clause, params = _account_filter_clause(account_name)
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
cur.execute(
|
cur.execute(
|
||||||
@@ -371,6 +372,7 @@ def web_dashboard(
|
|||||||
"upcoming_birthdays": upcoming_birthdays,
|
"upcoming_birthdays": upcoming_birthdays,
|
||||||
"last_sync": last_sync,
|
"last_sync": last_sync,
|
||||||
"last_sync_with_changes": last_sync_with_changes,
|
"last_sync_with_changes": last_sync_with_changes,
|
||||||
|
"groups": groups,
|
||||||
"current_year": datetime.now(Config.TIMEZONE).date().year,
|
"current_year": datetime.now(Config.TIMEZONE).date().year,
|
||||||
"today": datetime.now(Config.TIMEZONE).date(),
|
"today": datetime.now(Config.TIMEZONE).date(),
|
||||||
},
|
},
|
||||||
@@ -489,6 +491,7 @@ def web_search_special(
|
|||||||
|
|
||||||
with db.get_connection() as conn:
|
with db.get_connection() as conn:
|
||||||
rows = query_fn(conn, account_name)
|
rows = query_fn(conn, account_name)
|
||||||
|
groups = db.get_all_groups(conn, account_name)
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if not row.get("full_name"):
|
if not row.get("full_name"):
|
||||||
@@ -504,6 +507,7 @@ def web_search_special(
|
|||||||
"account_name": account_name or "alle Accounts",
|
"account_name": account_name or "alle Accounts",
|
||||||
"contacts": rows,
|
"contacts": rows,
|
||||||
"search": "",
|
"search": "",
|
||||||
|
"groups": groups,
|
||||||
"search_title": title_map.get(type, "Suche"),
|
"search_title": title_map.get(type, "Suche"),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -513,29 +517,41 @@ def web_search_special(
|
|||||||
def web_search(
|
def web_search(
|
||||||
request: Request,
|
request: Request,
|
||||||
search: str | None = Query(default=None),
|
search: str | None = Query(default=None),
|
||||||
|
group: str | None = Query(default=None),
|
||||||
current_user: str = Depends(get_current_user),
|
current_user: str = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
account_name, is_admin, show_all = _resolve_effective_account(request, current_user)
|
account_name, is_admin, show_all = _resolve_effective_account(request, current_user)
|
||||||
|
|
||||||
with db.get_connection() as conn:
|
with db.get_connection() as conn:
|
||||||
where_clause, params = _account_filter_clause(account_name)
|
groups = db.get_all_groups(conn, account_name)
|
||||||
search_clause = ""
|
|
||||||
if search:
|
|
||||||
search_op = "AND" if where_clause else "WHERE"
|
|
||||||
search_clause = f" {search_op} (full_name LIKE %s OR given_name LIKE %s OR family_name LIKE %s OR organization LIKE %s)"
|
|
||||||
like = f"%{search}%"
|
|
||||||
params.extend([like, like, like, like])
|
|
||||||
|
|
||||||
with conn.cursor() as cur:
|
if group:
|
||||||
cur.execute(
|
rows = db.get_contacts_by_group_uid(conn, account_name, group)
|
||||||
f"""SELECT id, full_name, given_name, middle_name, family_name,
|
search_title = None
|
||||||
prefix, suffix, organization, birthday, account, photo_url
|
for g in groups:
|
||||||
FROM contacts {where_clause}{search_clause}
|
if g["uid"] == group:
|
||||||
ORDER BY full_name
|
search_title = g["name"]
|
||||||
LIMIT 200""",
|
break
|
||||||
params,
|
else:
|
||||||
)
|
where_clause, params = _account_filter_clause(account_name)
|
||||||
rows = cur.fetchall()
|
search_clause = ""
|
||||||
|
if search:
|
||||||
|
search_op = "AND" if where_clause else "WHERE"
|
||||||
|
search_clause = f" {search_op} (full_name LIKE %s OR given_name LIKE %s OR family_name LIKE %s OR organization LIKE %s)"
|
||||||
|
like = f"%{search}%"
|
||||||
|
params.extend([like, like, like, like])
|
||||||
|
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
f"""SELECT id, full_name, given_name, middle_name, family_name,
|
||||||
|
prefix, suffix, organization, birthday, account, photo_url
|
||||||
|
FROM contacts {where_clause}{search_clause}
|
||||||
|
ORDER BY full_name
|
||||||
|
LIMIT 200""",
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
rows = cur.fetchall()
|
||||||
|
search_title = None
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if not row.get("full_name"):
|
if not row.get("full_name"):
|
||||||
@@ -551,6 +567,8 @@ def web_search(
|
|||||||
"account_name": account_name or "alle Accounts",
|
"account_name": account_name or "alle Accounts",
|
||||||
"contacts": rows,
|
"contacts": rows,
|
||||||
"search": search or "",
|
"search": search or "",
|
||||||
|
"groups": groups,
|
||||||
|
"search_title": search_title,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,29 @@
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.group-cloud {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.4rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 0.6rem;
|
||||||
|
background: #e7f3ff;
|
||||||
|
color: #0066cc;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-tag:hover {
|
||||||
|
background: #d0e8ff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 1450px) {
|
@media (max-width: 1450px) {
|
||||||
.special-searches-box {
|
.special-searches-box {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -52,6 +75,14 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="special-searches-box">
|
<div class="special-searches-box">
|
||||||
|
{% if groups %}
|
||||||
|
<div class="sidebar-section-title">Gruppen</div>
|
||||||
|
<div class="group-cloud">
|
||||||
|
{% for g in groups %}
|
||||||
|
<a href="/search?group={{ g.uid|urlquote }}" class="group-tag">{{ g.name or '(Ohne Name)' }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="sidebar-section-title">Special Searches</div>
|
<div class="sidebar-section-title">Special Searches</div>
|
||||||
<div class="sidebar-section-list">
|
<div class="sidebar-section-list">
|
||||||
<a href="/search/special?type=no_photo" class="special-search-link">ohne Bild</a>
|
<a href="/search/special?type=no_photo" class="special-search-link">ohne Bild</a>
|
||||||
|
|||||||
@@ -182,7 +182,7 @@
|
|||||||
<form class="search-form" method="get" action="/search">
|
<form class="search-form" method="get" action="/search">
|
||||||
<input type="text" name="search" value="{{ search }}" placeholder="Suche nach Name oder Organisation...">
|
<input type="text" name="search" value="{{ search }}" placeholder="Suche nach Name oder Organisation...">
|
||||||
<button type="submit">Suchen</button>
|
<button type="submit">Suchen</button>
|
||||||
{% if search %}
|
{% if search or search_title %}
|
||||||
<a href="/search">Zurücksetzen</a>
|
<a href="/search">Zurücksetzen</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -371,3 +371,30 @@ def get_group_count(conn, account: str | None) -> int:
|
|||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
cur.execute(f"SELECT COUNT(*) AS total FROM `groups` {where_clause}", params)
|
cur.execute(f"SELECT COUNT(*) AS total FROM `groups` {where_clause}", params)
|
||||||
return cur.fetchone()["total"]
|
return cur.fetchone()["total"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_groups(conn, account: str | None) -> list[dict]:
|
||||||
|
where_clause, params = _account_filter_clause(account)
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
f"SELECT id, name, uid FROM `groups` {where_clause} ORDER BY name",
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
return cur.fetchall()
|
||||||
|
|
||||||
|
|
||||||
|
def get_contacts_by_group_uid(conn, account: str | None, group_uid: str) -> list[dict]:
|
||||||
|
where_clause, params = _account_filter_clause(account)
|
||||||
|
group_op = "AND" if where_clause else "WHERE"
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
f"""SELECT c.id, c.full_name, c.given_name, c.middle_name, c.family_name,
|
||||||
|
c.prefix, c.suffix, c.organization, c.birthday, c.account, c.photo_url
|
||||||
|
FROM contacts c
|
||||||
|
JOIN group_members gm ON gm.member_uid = c.uid
|
||||||
|
JOIN `groups` g ON g.id = gm.group_id AND g.account = c.account
|
||||||
|
{where_clause} {group_op} g.uid = %s
|
||||||
|
ORDER BY c.full_name""",
|
||||||
|
params + [group_uid],
|
||||||
|
)
|
||||||
|
return cur.fetchall()
|
||||||
|
|||||||
Reference in New Issue
Block a user