diff --git a/hubzilla/addon/assoc_profile/assoc_profile.php b/hubzilla/addon/assoc_profile/assoc_profile.php
index 7784179..6daf5f4 100644
--- a/hubzilla/addon/assoc_profile/assoc_profile.php
+++ b/hubzilla/addon/assoc_profile/assoc_profile.php
@@ -150,6 +150,9 @@ function assoc_profile_content() {
if (function_exists('head_add_css')) {
head_add_css('/addon/assoc_profile/view/css/assoc_profile.css');
}
+ if (function_exists('head_add_js')) {
+ head_add_js('/addon/assoc_profile/view/js/assoc_profile.js');
+ }
$action = argv(1) ?? '';
@@ -158,29 +161,28 @@ function assoc_profile_content() {
}
if (!assoc_is_operator()) {
- return '
Operator access required.
';
+ return 'Operator access required.
';
}
- $sub = argv(2) ?? '';
- $slug = argv(3) ?? '';
+ $sub = argv(2) ?? '';
+ $slug = argv(3) ?? '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
return assoc_handle_post();
}
- // Manage index
if (!$sub) return assoc_render_manage_index();
- // Association actions
if ($sub === 'assoc') {
if (!$slug) return assoc_render_add_association_form();
return assoc_render_edit_association_form($slug);
}
- // Field actions
if ($sub === 'fields') return assoc_render_fields_form();
- return 'Unknown management action.
';
+ if ($sub === 'import') return assoc_render_import_form();
+
+ return 'Unknown management action.
';
}
// ----------------------------------------------------------------------------
@@ -189,19 +191,23 @@ function assoc_profile_content() {
function assoc_handle_post() {
if (!assoc_verify_csrf()) {
- return 'Invalid form token.
';
+ return 'Invalid form token.
';
}
$action = $_POST['assoc_action'] ?? '';
switch ($action) {
- case 'save_association': return assoc_save_association();
- case 'add_association': return assoc_add_association();
- case 'save_fields': return assoc_save_fields();
- case 'add_field': return assoc_add_field();
- case 'remove_field': return assoc_remove_field();
+ case 'save_association': return assoc_save_association();
+ case 'add_association': return assoc_add_association();
+ case 'delete_association':return assoc_delete_association();
+ case 'save_fields': return assoc_save_fields();
+ case 'add_field': return assoc_add_field();
+ case 'remove_field': return assoc_remove_field();
+ case 'export_selected': return assoc_handle_export($_POST['export_slugs'] ?? '');
+ case 'import_upload': return assoc_handle_import_upload();
+ case 'import_confirm': return assoc_handle_import_confirm();
default:
- return 'Unknown action.
';
+ return 'Unknown action.
';
}
}
@@ -256,7 +262,29 @@ function assoc_add_association() {
if (assoc_write_registry($registry)) {
goaway(z_root() . '/assoc_profile/manage/assoc/' . $slug);
}
- return 'Failed to create association.
';
+ return 'Failed to create association.
';
+}
+
+// ----------------------------------------------------------------------------
+// DELETE ASSOCIATION
+// ----------------------------------------------------------------------------
+
+function assoc_delete_association() {
+ $slug = substr(strip_tags($_POST['assoc_slug'] ?? ''), 0, 128);
+ if (!$slug) return 'Missing slug.
';
+
+ $registry = assoc_load_registry();
+ if (!isset($registry[$slug])) {
+ return 'Association not found.
';
+ }
+
+ $name = $registry[$slug]['assoc_legal_name'] ?? $slug;
+ unset($registry[$slug]);
+
+ if (assoc_write_registry($registry)) {
+ goaway(z_root() . '/assoc_profile/manage');
+ }
+ return 'Delete failed. Check server logs.
';
}
// ----------------------------------------------------------------------------
diff --git a/hubzilla/addon/assoc_profile/assoc_profile_manage.php b/hubzilla/addon/assoc_profile/assoc_profile_manage.php
index af8cf19..bfc04bc 100644
--- a/hubzilla/addon/assoc_profile/assoc_profile_manage.php
+++ b/hubzilla/addon/assoc_profile/assoc_profile_manage.php
@@ -2,72 +2,201 @@
/**
* Association Profile — Management Renderer
- * Render functions for /assoc_profile/manage interface.
* Operator-only. Called from assoc_profile.php content router.
+ * v0.2.0 — search, filter, paginate, bulk export, import with diff, delete.
*/
+// ----------------------------------------------------------------------------
+// MANAGE INDEX
+// ----------------------------------------------------------------------------
+
function assoc_render_manage_index() {
$registry = assoc_load_registry();
- $out = '';
- $out .= '
Association Profile — Management ';
- $out .= '
';
- if (empty($registry)) {
- $out .= '
No associations registered.
';
- } else {
- $out .= '
';
- $out .= 'Slug Legal Name Type Units Updated ';
- foreach ($registry as $slug => $entry) {
- $out .= '';
- $out .= '' . assoc_h($slug) . ' ';
- $out .= '' . assoc_h($entry['assoc_legal_name'] ?? '—') . ' ';
- $out .= '' . assoc_h($entry['assoc_type'] ?? '—') . ' ';
- $out .= '' . assoc_h($entry['assoc_units'] ?? '—') . ' ';
- $out .= '' . assoc_h($entry['assoc_updated'] ?? '—') . ' ';
- $out .= 'Edit ';
- $out .= ' ';
- }
- $out .= '
';
+ $def = assoc_load_fields();
+ $fields = $def['fields'] ?? [];
+
+ // Build filter option sets from registry data
+ $counties = [];
+ $types = [];
+ $statuses = [];
+ foreach ($registry as $slug => $entry) {
+ if (!empty($entry['assoc_county'])) $counties[$entry['assoc_county']] = true;
+ if (!empty($entry['assoc_type'])) $types[$entry['assoc_type']] = true;
+ if (!empty($entry['assoc_sos_standing'])) $statuses[$entry['assoc_sos_standing']] = true;
}
+ ksort($counties); ksort($types); ksort($statuses);
+
+ $total = count($registry);
+
+ $out = '
';
+
+ // Header
+ $out .= '';
+
+ if (empty($registry)) {
+ $out .= '
';
+ $out .= '
';
+ return $out;
+ }
+
+ // Search and filter bar
+ $out .= '
';
+ $out .= '
Search ';
+ $out .= '
';
+
+ $out .= '
County ';
+ $out .= 'All counties ';
+ foreach ($counties as $c => $_) { $out .= '' . assoc_h($c) . ' '; }
+ $out .= '
';
+
+ $out .= '
Type ';
+ $out .= 'All types ';
+ foreach ($types as $t => $_) { $out .= '' . assoc_h($t === 'UNK' ? 'Unknown' : $t) . ' '; }
+ $out .= '
';
+
+ $out .= '
Standing ';
+ $out .= 'All ';
+ foreach ($statuses as $s => $_) { $out .= '' . assoc_h($s === 'UNK' ? 'Unknown' : $s) . ' '; }
+ $out .= '
';
+ $out .= '
';
+
+ // Bulk bar (hidden until selection)
+ $out .= '
';
+ $out .= '0 selected ';
+ $out .= 'Export selected ';
+ $out .= '
';
+
+ // Hidden export form
+ $out .= '
';
+
+ // Table
+ $out .= '
';
+
+ // Pagination controls (JS-driven)
+ $out .= '';
+
$out .= '
';
return $out;
}
+// ----------------------------------------------------------------------------
+// ADD ASSOCIATION
+// ----------------------------------------------------------------------------
+
function assoc_render_add_association_form() {
$out = '';
- $out .= '
Add Association ';
+ $out .= '';
+
+ $out .= '
';
+ $out .= '
Create Association ';
+ $out .= '
';
return $out;
}
+// ----------------------------------------------------------------------------
+// EDIT ASSOCIATION
+// ----------------------------------------------------------------------------
+
function assoc_render_edit_association_form($slug) {
$registry = assoc_load_registry();
if (!isset($registry[$slug])) {
- return 'Association not found: ' . assoc_h($slug) . '
';
- }
- $entry = $registry[$slug];
- $def = assoc_load_fields();
- $groups = $def['groups'] ?? [];
- $fields = $def['fields'] ?? [];
- $by_group = [];
- foreach ($fields as $f) {
- $by_group[$f['group']][] = $f;
+ return 'Association not found: ' . assoc_h($slug) . '
';
}
+ $entry = $registry[$slug];
+ $def = assoc_load_fields();
+ $groups = $def['groups'] ?? [];
+ $fields = $def['fields'] ?? [];
+ $by_group = [];
+ foreach ($fields as $f) { $by_group[$f['group']][] = $f; }
+
+ $name = $entry['assoc_legal_name'] ?? $slug;
+
$out = '';
- $out .= '
' . assoc_h($entry['assoc_legal_name'] ?? $slug) . ' ';
- $out .= '
' . assoc_h($slug) . '
';
+ $out .= '';
+
$out .= '
';
+ // Hidden remove forms
foreach ($fields as $f) {
if (($f['type'] ?? '') === 'readonly') continue;
$nick = $f['nickname'];
- $out .= '
';
+ $out .= ' ';
$out .= assoc_csrf_token();
$out .= ' ';
$out .= ' ';
$out .= ' ';
}
- $out .= '
Add New Field ';
+ // Add new field
+ $out .= '