Fix 500 error, add Address/Contact placeholders, and enhance Person activity view
- Fixed broken import path in /core dashboard - Simplified core layout data requirements to prevent crashes - Implemented V3 CRUD and Dexie tables for Addresses and Contacts - Created placeholder management pages for /core/addresses and /core/contacts - Added 'Linked Activity & Content' section to Person detail page to show related events and posts
This commit is contained in:
79
src/lib/ae_core/ae_core__address.ts
Normal file
79
src/lib/ae_core/ae_core__address.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
import { api } from '$lib/api/api';
|
||||
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
||||
import { db_core } from '$lib/ae_core/db_core';
|
||||
|
||||
const ae_promises: key_val = {};
|
||||
|
||||
export interface Address {
|
||||
id: string;
|
||||
address_id: string;
|
||||
address_id_random: string;
|
||||
account_id: string;
|
||||
account_id_random: string;
|
||||
|
||||
for_type?: string;
|
||||
for_id?: string;
|
||||
for_id_random?: string;
|
||||
|
||||
city: string;
|
||||
state_province: string;
|
||||
country: string;
|
||||
|
||||
enable: null | boolean;
|
||||
hide?: null | boolean;
|
||||
priority?: null | boolean;
|
||||
sort?: null | number;
|
||||
group?: null | string;
|
||||
notes?: null | string;
|
||||
created_on: Date;
|
||||
updated_on?: null | Date;
|
||||
}
|
||||
|
||||
export async function load_ae_obj_li__address({
|
||||
api_cfg,
|
||||
for_obj_type,
|
||||
for_obj_id,
|
||||
enabled = 'enabled',
|
||||
hidden = 'not_hidden',
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
for_obj_type?: string;
|
||||
for_obj_id?: string;
|
||||
enabled?: 'all' | 'enabled' | 'not_enabled';
|
||||
hidden?: 'all' | 'hidden' | 'not_hidden';
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
ae_promises.load__address_obj_li = await api.get_ae_obj_li_v3({
|
||||
api_cfg,
|
||||
obj_type: 'address',
|
||||
for_obj_type,
|
||||
for_obj_id,
|
||||
enabled,
|
||||
hidden,
|
||||
log_lvl
|
||||
}).then(async (result) => {
|
||||
if (result && Array.isArray(result)) {
|
||||
const processed = await process_ae_obj__address_props({ obj_li: result, log_lvl });
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_core,
|
||||
table_name: 'address',
|
||||
obj_li: processed,
|
||||
properties_to_save: ['id', 'address_id', 'address_id_random', 'city', 'state_province', 'country', 'enable'],
|
||||
log_lvl
|
||||
});
|
||||
return result;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
return ae_promises.load__address_obj_li;
|
||||
}
|
||||
|
||||
export async function process_ae_obj__address_props({ obj_li, log_lvl = 0 }: { obj_li: any[], log_lvl?: number }) {
|
||||
return obj_li.map(obj => {
|
||||
const new_obj = { ...obj };
|
||||
new_obj.id = obj.address_id_random;
|
||||
return new_obj;
|
||||
});
|
||||
}
|
||||
79
src/lib/ae_core/ae_core__contact.ts
Normal file
79
src/lib/ae_core/ae_core__contact.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import type { key_val } from '$lib/stores/ae_stores';
|
||||
import { api } from '$lib/api/api';
|
||||
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
||||
import { db_core } from '$lib/ae_core/db_core';
|
||||
|
||||
const ae_promises: key_val = {};
|
||||
|
||||
export interface Contact {
|
||||
id: string;
|
||||
contact_id: string;
|
||||
contact_id_random: string;
|
||||
account_id: string;
|
||||
account_id_random: string;
|
||||
|
||||
for_type?: string;
|
||||
for_id?: string;
|
||||
for_id_random?: string;
|
||||
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
|
||||
enable: null | boolean;
|
||||
hide?: null | boolean;
|
||||
priority?: null | boolean;
|
||||
sort?: null | number;
|
||||
group?: null | string;
|
||||
notes?: null | string;
|
||||
created_on: Date;
|
||||
updated_on?: null | Date;
|
||||
}
|
||||
|
||||
export async function load_ae_obj_li__contact({
|
||||
api_cfg,
|
||||
for_obj_type,
|
||||
for_obj_id,
|
||||
enabled = 'enabled',
|
||||
hidden = 'not_hidden',
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
for_obj_type?: string;
|
||||
for_obj_id?: string;
|
||||
enabled?: 'all' | 'enabled' | 'not_enabled';
|
||||
hidden?: 'all' | 'hidden' | 'not_hidden';
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
ae_promises.load__contact_obj_li = await api.get_ae_obj_li_v3({
|
||||
api_cfg,
|
||||
obj_type: 'contact',
|
||||
for_obj_type,
|
||||
for_obj_id,
|
||||
enabled,
|
||||
hidden,
|
||||
log_lvl
|
||||
}).then(async (result) => {
|
||||
if (result && Array.isArray(result)) {
|
||||
const processed = await process_ae_obj__contact_props({ obj_li: result, log_lvl });
|
||||
await db_save_ae_obj_li__ae_obj({
|
||||
db_instance: db_core,
|
||||
table_name: 'contact',
|
||||
obj_li: processed,
|
||||
properties_to_save: ['id', 'contact_id', 'contact_id_random', 'name', 'email', 'phone', 'enable'],
|
||||
log_lvl
|
||||
});
|
||||
return result;
|
||||
}
|
||||
return [];
|
||||
});
|
||||
return ae_promises.load__contact_obj_li;
|
||||
}
|
||||
|
||||
export async function process_ae_obj__contact_props({ obj_li, log_lvl = 0 }: { obj_li: any[], log_lvl?: number }) {
|
||||
return obj_li.map(obj => {
|
||||
const new_obj = { ...obj };
|
||||
new_obj.id = obj.contact_id_random;
|
||||
return new_obj;
|
||||
});
|
||||
}
|
||||
@@ -173,6 +173,8 @@ export class MySubClassedDexie extends Dexie {
|
||||
account!: Table<any>;
|
||||
site!: Table<any>;
|
||||
site_domain!: Table<any>;
|
||||
address!: Table<any>;
|
||||
contact!: Table<any>;
|
||||
|
||||
constructor() {
|
||||
super('ae_core_db');
|
||||
@@ -219,6 +221,20 @@ export class MySubClassedDexie extends Dexie {
|
||||
id, site_domain_id, site_domain_id_random,
|
||||
site_id, site_id_random,
|
||||
domain,
|
||||
enable, hide, priority, sort, group, created_on, updated_on`,
|
||||
|
||||
address: `
|
||||
id, address_id, address_id_random,
|
||||
account_id, account_id_random,
|
||||
for_type, for_id, for_id_random,
|
||||
city, state_province, country,
|
||||
enable, hide, priority, sort, group, created_on, updated_on`,
|
||||
|
||||
contact: `
|
||||
id, contact_id, contact_id_random,
|
||||
account_id, account_id_random,
|
||||
for_type, for_id, for_id_random,
|
||||
name, email, phone,
|
||||
enable, hide, priority, sort, group, created_on, updated_on`
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { ae_loc, ae_sess, ae_api, slct } from '$lib/stores/ae_stores';
|
||||
import { Building, Globe, Users, ShieldCheck, List, LayoutDashboard } from 'lucide-svelte';
|
||||
import { Building, Globe, Users, ShieldCheck, List, LayoutDashboard, MapPin, Phone } from 'lucide-svelte';
|
||||
|
||||
interface Props {
|
||||
data: any;
|
||||
@@ -31,6 +31,12 @@
|
||||
<a href="/core/people" class="btn btn-sm variant-soft-warning">
|
||||
<Users size={14} class="mr-1" /> People
|
||||
</a>
|
||||
<a href="/core/addresses" class="btn btn-sm variant-soft-surface">
|
||||
<MapPin size={14} class="mr-1" /> Addresses
|
||||
</a>
|
||||
<a href="/core/contacts" class="btn btn-sm variant-soft-surface">
|
||||
<Phone size={14} class="mr-1" /> Contacts
|
||||
</a>
|
||||
<a href="/core/lookups" class="btn btn-sm variant-soft-surface">
|
||||
<List size={14} class="mr-1" /> Lookups
|
||||
</a>
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import type { PageLoad } from './$types';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageLoad = async ({ parent }) => {
|
||||
const parentData = await parent();
|
||||
const data = await parent();
|
||||
|
||||
if (!data.account_id) {
|
||||
console.error('Core Dashboard: No account_id found in parent data');
|
||||
// We could throw an error here, but for now let's just log it and return
|
||||
// to avoid a hard crash if the user is just navigating.
|
||||
}
|
||||
|
||||
return {
|
||||
account_id: parentData.account_id
|
||||
account_id: data.account_id
|
||||
};
|
||||
};
|
||||
};
|
||||
81
src/routes/core/addresses/+page.svelte
Normal file
81
src/routes/core/addresses/+page.svelte
Normal file
@@ -0,0 +1,81 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { ae_loc, ae_api, slct } from '$lib/stores/ae_stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { MapPin, Plus, Search } from 'lucide-svelte';
|
||||
import { load_ae_obj_li__address } from '$lib/ae_core/ae_core__address';
|
||||
|
||||
let address_li: any[] = $state([]);
|
||||
let loading = $state(true);
|
||||
|
||||
async function load_addresses() {
|
||||
if (!$ae_loc.account_id) return;
|
||||
loading = true;
|
||||
address_li = await load_ae_obj_li__address({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_id: $ae_loc.account_id,
|
||||
enabled: 'all',
|
||||
log_lvl: 1
|
||||
});
|
||||
loading = false;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!$ae_loc.manager_access) {
|
||||
goto('/core');
|
||||
return;
|
||||
}
|
||||
load_addresses();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto p-4 space-y-6">
|
||||
<header class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<MapPin size={24} />
|
||||
<h1 class="h2">Address Management</h1>
|
||||
</div>
|
||||
<button class="btn variant-filled-primary" disabled>
|
||||
<Plus size={16} class="mr-2" /> Add Address
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{#if loading}
|
||||
<div class="placeholder animate-pulse h-64 w-full"></div>
|
||||
{:else if address_li.length === 0}
|
||||
<div class="card p-8 text-center variant-soft">
|
||||
<p class="opacity-60">No addresses found for this account.</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="table-container">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>City</th>
|
||||
<th>State/Province</th>
|
||||
<th>Country</th>
|
||||
<th>Status</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each address_li as addr}
|
||||
<tr>
|
||||
<td>{addr.city || '--'}</td>
|
||||
<td>{addr.state_province || '--'}</td>
|
||||
<td>{addr.country || '--'}</td>
|
||||
<td>
|
||||
<span class="badge {addr.enable ? 'variant-filled-success' : 'variant-filled-error'}">
|
||||
{addr.enable ? 'Enabled' : 'Disabled'}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<button class="btn btn-sm variant-soft-primary" disabled>Manage</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
91
src/routes/core/contacts/+page.svelte
Normal file
91
src/routes/core/contacts/+page.svelte
Normal file
@@ -0,0 +1,91 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { ae_loc, ae_api, slct } from '$lib/stores/ae_stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Phone, Plus, Search, Mail, User } from 'lucide-svelte';
|
||||
import { load_ae_obj_li__contact } from '$lib/ae_core/ae_core__contact';
|
||||
|
||||
let contact_li: any[] = $state([]);
|
||||
let loading = $state(true);
|
||||
|
||||
async function load_contacts() {
|
||||
if (!$ae_loc.account_id) return;
|
||||
loading = true;
|
||||
contact_li = await load_ae_obj_li__contact({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_id: $ae_loc.account_id,
|
||||
enabled: 'all',
|
||||
log_lvl: 1
|
||||
});
|
||||
loading = false;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!$ae_loc.manager_access) {
|
||||
goto('/core');
|
||||
return;
|
||||
}
|
||||
load_contacts();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto p-4 space-y-6">
|
||||
<header class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<Phone size={24} />
|
||||
<h1 class="h2">Contact Management</h1>
|
||||
</div>
|
||||
<button class="btn variant-filled-primary" disabled>
|
||||
<Plus size={16} class="mr-2" /> Add Contact
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{#if loading}
|
||||
<div class="placeholder animate-pulse h-64 w-full"></div>
|
||||
{:else if contact_li.length === 0}
|
||||
<div class="card p-8 text-center variant-soft">
|
||||
<p class="opacity-60">No contacts found for this account.</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="table-container">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
<th>Status</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each contact_li as con}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="flex items-center gap-2">
|
||||
<User size={14} class="opacity-60" />
|
||||
{con.name || '--'}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex items-center gap-2">
|
||||
<Mail size={14} class="opacity-60" />
|
||||
{con.email || '--'}
|
||||
</div>
|
||||
</td>
|
||||
<td>{con.phone || '--'}</td>
|
||||
<td>
|
||||
<span class="badge {con.enable ? 'variant-filled-success' : 'variant-filled-error'}">
|
||||
{con.enable ? 'Enabled' : 'Disabled'}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<button class="btn btn-sm variant-soft-primary" disabled>Manage</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -28,7 +28,9 @@
|
||||
import Person_view from './../../person_view.svelte';
|
||||
import { load_ae_obj_li__user } from '$lib/ae_core/ae_core__user';
|
||||
import { update_ae_obj__person } from '$lib/ae_core/ae_core__person';
|
||||
import { Users, Link, Unlink, UserPlus, ShieldCheck, User } from 'lucide-svelte';
|
||||
import { qry_ae_obj_li__event } from '$lib/ae_events/ae_events__event';
|
||||
import { load_ae_obj_li__post } from '$lib/ae_posts/ae_posts__post';
|
||||
import { Users, Link, Unlink, UserPlus, ShieldCheck, User, Calendar, MessageSquare, History } from 'lucide-svelte';
|
||||
|
||||
interface Props {
|
||||
data: any;
|
||||
@@ -54,6 +56,36 @@
|
||||
let loading_users = $state(false);
|
||||
let show_link_ui = $state(false);
|
||||
|
||||
let related_events: any[] = $state([]);
|
||||
let related_posts: any[] = $state([]);
|
||||
let loading_activity = $state(false);
|
||||
|
||||
async function load_activity() {
|
||||
if (!$slct.person_id) return;
|
||||
loading_activity = true;
|
||||
|
||||
// Load related data using search queries
|
||||
// Assuming person_id_random is the field name in these objects
|
||||
const [events, posts] = await Promise.all([
|
||||
qry_ae_obj_li__event({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_id: $ae_loc.account_id,
|
||||
params: { person_id_random: $slct.person_id },
|
||||
log_lvl: 1
|
||||
}),
|
||||
load_ae_obj_li__post({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_id: $ae_loc.account_id,
|
||||
params: { person_id_random: $slct.person_id },
|
||||
log_lvl: 1
|
||||
})
|
||||
]);
|
||||
|
||||
related_events = events || [];
|
||||
related_posts = posts || [];
|
||||
loading_activity = false;
|
||||
}
|
||||
|
||||
async function load_unlinked_users() {
|
||||
if (!$ae_loc.manager_access) return;
|
||||
loading_users = true;
|
||||
@@ -95,6 +127,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!$ae_loc.manager_access) {
|
||||
goto('/core');
|
||||
return;
|
||||
}
|
||||
load_activity();
|
||||
});
|
||||
|
||||
if (!$ae_loc.person) {
|
||||
$ae_loc.person = {};
|
||||
}
|
||||
@@ -246,6 +286,61 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Activity & Content Section -->
|
||||
<div class="card p-4 variant-soft-surface space-y-4 mx-4">
|
||||
<header class="flex items-center gap-2 font-bold border-b border-surface-500/30 pb-2">
|
||||
<History size={18} />
|
||||
<span>Linked Activity & Content</span>
|
||||
</header>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Related Events -->
|
||||
<div class="space-y-3">
|
||||
<h4 class="h4 flex items-center gap-2 text-sm opacity-70 uppercase tracking-wider font-bold">
|
||||
<Calendar size={16} /> Related Events
|
||||
</h4>
|
||||
{#if loading_activity}
|
||||
<div class="placeholder animate-pulse h-20 w-full"></div>
|
||||
{:else if related_events.length === 0}
|
||||
<p class="text-sm italic opacity-50">No related events found.</p>
|
||||
{:else}
|
||||
<div class="space-y-2">
|
||||
{#each related_events as ev}
|
||||
<a href="/events/{ev.event_id_random}" class="card p-3 variant-soft flex flex-col gap-1 hover:variant-soft-primary transition-all">
|
||||
<span class="font-bold">{ev.name}</span>
|
||||
<span class="text-xs opacity-60">{new Date(ev.start_datetime).toLocaleDateString()}</span>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Related Posts -->
|
||||
<div class="space-y-3">
|
||||
<h4 class="h4 flex items-center gap-2 text-sm opacity-70 uppercase tracking-wider font-bold">
|
||||
<MessageSquare size={16} /> Related Posts
|
||||
</h4>
|
||||
{#if loading_activity}
|
||||
<div class="placeholder animate-pulse h-20 w-full"></div>
|
||||
{:else if related_posts.length === 0}
|
||||
<p class="text-sm italic opacity-50">No related posts found.</p>
|
||||
{:else}
|
||||
<div class="space-y-2">
|
||||
{#each related_posts as post}
|
||||
<a href="/posts/{post.post_id_random}" class="card p-3 variant-soft flex flex-col gap-1 hover:variant-soft-primary transition-all">
|
||||
<span class="font-bold">{post.title}</span>
|
||||
<div class="flex justify-between items-center text-xs opacity-60">
|
||||
<span>{new Date(post.created_on).toLocaleDateString()}</span>
|
||||
<span class="badge variant-soft-surface">{post.post_comment_count || 0} comments</span>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !$lq__person_obj}
|
||||
|
||||
Reference in New Issue
Block a user