--- task: Cooperative officer/member homepages, chapter org chart, member search, create user, assign officer to child chapter, create chapter, share registration link cycles: 5 context: true private: false started: 2026-05-29T00:00:00Z finished: 2026-05-29T00:01:00Z --- ## prerequisite This plan DEPENDS on plan `4fc3b455fb62b15dfb06790a1f421c96` (COOP_MEMBER/COOP_OFFICER types + migrations). Execute that plan first. Assumes the following already exist after that plan runs: - `users.acct_type` values `'coop officer'` and `'coop member'` (UserTypes enum) - `chapters.cooperative_id` column + `chapters.level` enum includes `'municipal'` - `chapter_members.role` column - `UserActions::ViewChapterOrgChart`, `ManageChapterMembers`, `ViewScopedMemberReports`, `AssignChapterOfficer` - `UserTypes::COOP_OFFICER` and `UserTypes::COOP_MEMBER` in UserPermissions::roles() - `isCoopOfficer` and `isCoopMember` in `useAuth.js` - `Home.vue` fragments wired for COOP_OFFICER and COOP_MEMBER ## files - `app/Http/Controllers/Support/ChapterController.php` — add orgChart(), officerAssign(), createChapter(), memberSearch() methods - `app/Http/Controllers/Market/CooperativeController.php` [line 410] — extend publicRegisterMember to accept chapter_hash; auto-assign to chapter - `routes/web.php` [lines 761-770] — register new chapter endpoints + public chapter route - `app/Http/Controllers/Support/VueRouteMap.php` [lines 278-311] — register new SPA pages; add coop officer/member to existing coop allowedUserTypes - `resources/js/Pages/Fragments/Home/HomeCoopOfficer.vue` — NEW: level-aware officer dashboard - `resources/js/Pages/Fragments/Home/HomeCoopMember.vue` — NEW: read-only member card - `resources/js/Pages/ChapterOrgChart.vue` — NEW: org chart with child chapter toggle - `resources/js/Pages/CoopMemberSearch.vue` — NEW: name-only member search for officers - `resources/js/Pages/CreateCoopUser.vue` — NEW: full user creation with coop+chapter auto-assign - `resources/js/Pages/AssignChapterOfficer.vue` — NEW: move member to child chapter as officer - `resources/js/Pages/CreateChapter.vue` — NEW: create chapter one level below current officer's chapter - `resources/js/composables/useChapters.js` — add fetchOfficerScope(), fetchOrgChart(), searchMembers(), assignOfficer(), createChapter() - `routes/web.php` [line ~754] — add public chapter info + registration endpoints ## level hierarchy constants ``` LEVEL_ORDER = ['national','region','province','city','municipal','barangay'] CHILD_LEVELS = { 'national' => ['region'], 'region' => ['province'], 'province' => ['city','municipal'], 'city' => ['barangay'], 'municipal' => ['barangay'], 'barangay' => [], } ``` All PHP code using level must use `in_array($level, ['city','municipal'])` never equality check. ## steps ### 1. ChapterController — getOrgChart() Add `public function getOrgChart(Request $request)` to `ChapterController.php`: - Auth: caller must be logged in - Determine caller's chapter: `ChapterMember::with('chapter')->where('user_id', Auth::id())->where('is_active', true)->orderByRaw("FIELD(chapters.level, 'region','province','city','municipal','barangay')")->join('chapters','chapters.id','chapter_members.chapter_id')->first()` - For COOP_OFFICER: load own chapter + its officers (chapter_members WHERE role IS NOT NULL AND role != 'MEMBER') + direct child chapters (WHERE parent_id = own chapter id AND cooperative_id = own cooperative_id) each with `active_members_count` and their own officers (collapsed by default) - For COOP_MEMBER: load own chapter name + officers only (names + roles) — do NOT return member list - For Big3/COORDINATOR: accept optional `cooperative_id` param, return full coop chapter tree - Response shape: ```json { "own_chapter": { "id", "hashkey", "name", "level", "location_key", "officers": [{"name","role","position"}] }, "children": [{ "id", "hashkey", "name", "level", "member_count": 42, "officers": [] }] } ``` Children `officers` array starts empty (populated by frontend on toggle via second call to same endpoint with `chapter_id` param pointing to that child). ### 2. ChapterController — assignOfficer() Add `public function assignOfficer(Request $request)`: - Validate: `member_user_hashkey` (string, required), `child_chapter_id` (integer, required), `role` (string, required: PRESIDENT/VICE_PRESIDENT/SECRETARY/TREASURER/AUDITOR/BOARD_MEMBER) - Permission check: caller must be COOP_OFFICER (or Big3/COORDINATOR) - Eligibility check A — the selected user: must have a chapter_members row in caller's OWN chapter (`is_active = true`), AND role IS NULL OR role = 'MEMBER' (not already an officer there) - Eligibility check B — child_chapter_id: must have `parent_id = caller's own chapter id` AND `cooperative_id = caller's cooperative_id` - Effect: 1. Delete the member's existing chapter_members row in the parent chapter 2. Insert/update chapter_members row: `{user_id, chapter_id: child_chapter_id, role, position: role label, is_manual_override: true, assigned_by: Auth::id(), is_active: true, created_by, updated_by, hashkey}` 3. Upgrade `users.acct_type = 'coop officer'` if currently `'coop member'` - Return `{ success: true, message: '...' }` ### 3. ChapterController — createChapter() Add `public function createChapter(Request $request)`: - Validate: `name` (string, required), `location_key` (string, required), `lat` (nullable decimal), `lng` (nullable decimal) - Permission check: caller must be COOP_OFFICER (or Big3/COORDINATOR) - Determine caller's chapter level → compute allowed child level using CHILD_LEVELS map - If caller is at BARANGAY level, return 422 "Cannot create sub-chapters at barangay level" - Determine caller's cooperative_id from their chapter's cooperative_id - Create Chapter: `{ name, level: childLevel, parent_id: callerChapterId, cooperative_id: callerCoopId, location_key: strtolower(trim()), lat, lng, is_active: true, created_by, updated_by, hashkey }` - Return `{ success: true, chapter: { hashkey, name, level } }` ### 4. ChapterController — memberSearch() Add `public function memberSearch(Request $request)`: - Validate: `query` (string, required, min 2) - Permission check: caller must have `ManageChapterMembers` action (COOP_OFFICER, COORDINATOR, Big3) - Scope: collect all chapter IDs in caller's subtree (own chapter + all descendants recursively via `parent_id` chain, scoped to same `cooperative_id`) - Query: `User::join('chapter_members','users.id','chapter_members.user_id')->whereIn('chapter_members.chapter_id', $scopeIds)->where('chapter_members.is_active', true)->where('users.name', 'LIKE', "%{$query}%")->select('users.name', 'chapter_members.role', 'chapters.name as chapter_name')->join('chapters','chapters.id','chapter_members.chapter_id')->distinct()->limit(30)->get()` - Return ONLY: `[{ name, role, chapter_name }]` — NO hashkey, NO mobile_number, NO username, NO other fields ### 5. ChapterController — getOfficerScope() Add `public function getOfficerScope(Request $request)`: - Returns caller's own chapter info + list of direct child chapters (for dropdowns in AssignChapterOfficer and CreateChapter pages) - Response: `{ own_chapter: {id, hashkey, name, level}, child_chapters: [{id, hashkey, name, level, active_members_count}], cooperative: {hashkey, name}, eligible_members: [{user_hashkey, name, role}] }` - `eligible_members`: chapter_members WHERE chapter_id = own chapter AND is_active = true AND (role IS NULL OR role = 'MEMBER') → join users → return {user_hashkey: users.hashkey, name: users.name OR users.fullname, role} ### 6. Public chapter registration endpoint In `routes/web.php` add after existing public coop routes (~line 755): ```php Route::get('/api/public/chapter/{hkey}', [\App\Http\Controllers\Support\ChapterController::class, 'publicGetChapter']); Route::post('/api/public/chapter/register', [\App\Http\Controllers\Support\ChapterController::class, 'publicRegisterToChapter']); ``` Add `publicGetChapter(Request $request, string $hkey)` to ChapterController: - No auth required - Return chapter name, level, cooperative name — nothing else - Response: `{ success: true, chapter: { name, level }, cooperative: { name } }` Add `publicRegisterToChapter(Request $request)` to ChapterController: - No auth required - Params: `chapter_hash` (string), `name`, `username`, `mobile_number`, `password` - Validate same as publicRegisterMember - Load chapter by hashkey; load cooperative from chapter.cooperative_id - Create user: acct_type = 'coop member', parentuid = chapter.created_by (fallback: first COORDINATOR) - Insert cooperative_members row linking user to cooperative - Insert chapter_members row: `{user_id, chapter_id: chapter.id, is_manual_override: false, is_active: true, created_by: user.id, updated_by: user.id, hashkey}` - Update user.settings.cooperatives = [cooperative.hashkey] - Return `{ success: true, user_hashkey, message }` ### 7. routes/web.php — register new chapter endpoints In the Chapter routes block (lines 761–770), add: ```php Route::post('/Chapters/OrgChart', [\App\Http\Controllers\Support\ChapterController::class, 'getOrgChart'], ['middleware' => 'auth']); Route::post('/Chapters/Officer/Assign', [\App\Http\Controllers\Support\ChapterController::class, 'assignOfficer'], ['middleware' => 'auth']); Route::post('/Chapters/Create', [\App\Http\Controllers\Support\ChapterController::class, 'createChapter'], ['middleware' => 'auth']); Route::post('/Chapters/Members/Search', [\App\Http\Controllers\Support\ChapterController::class, 'memberSearch'], ['middleware' => 'auth']); Route::post('/Chapters/Officer/Scope', [\App\Http\Controllers\Support\ChapterController::class, 'getOfficerScope'], ['middleware' => 'auth']); ``` ### 8. home-data — COOP_OFFICER and COOP_MEMBER branches In the `/home-data` closure in `routes/web.php` (after the COORDINATOR block, ~line 153): ```php if ($acctType === \App\Enums\UserTypes::COOP_OFFICER && $user) { $myChapterMember = \App\Models\ChapterMember::join('chapters','chapters.id','chapter_members.chapter_id') ->where('chapter_members.user_id', $user->id)->where('chapter_members.is_active', true) ->orderByRaw("FIELD(chapters.level,'region','province','city','municipal','barangay')") ->select('chapter_members.*','chapters.name as chapter_name','chapters.level as chapter_level','chapters.hashkey as chapter_hashkey','chapters.cooperative_id') ->first(); if ($myChapterMember) { $myChapterId = $myChapterMember->chapter_id; $chapterMemberCount = \App\Models\ChapterMember::where('chapter_id', $myChapterId)->where('is_active', true)->count(); $childChapterCount = \App\Models\Chapter::where('parent_id', $myChapterId)->where('is_active', true)->count(); $newMembersCount = \App\Models\ChapterMember::where('chapter_id', $myChapterId)->where('is_active', true)->where('created_at', '>=', now()->subDays(7))->count(); $cooperativeHash = \App\Models\Market\Organization::where('id', $myChapterMember->cooperative_id)->value('hashkey'); $props['props']['chapter_info'] = [ 'chapter_name' => $myChapterMember->chapter_name, 'chapter_level' => $myChapterMember->chapter_level, 'chapter_hashkey' => $myChapterMember->chapter_hashkey, 'cooperative_hash' => $cooperativeHash, ]; $props['props']['stats']['chapter_member_count'] = $chapterMemberCount; $props['props']['stats']['child_chapter_count'] = $childChapterCount; $props['props']['stats']['new_members_7d'] = $newMembersCount; } } if ($acctType === \App\Enums\UserTypes::COOP_MEMBER && $user) { $myChapterMember = \App\Models\ChapterMember::join('chapters','chapters.id','chapter_members.chapter_id') ->where('chapter_members.user_id', $user->id)->where('chapter_members.is_active', true) ->orderByRaw("FIELD(chapters.level,'barangay','city','municipal','province','region','national')") ->select('chapter_members.*','chapters.name as chapter_name','chapters.level as chapter_level','chapters.id as chapter_id','chapters.cooperative_id') ->first(); if ($myChapterMember) { $chapterMemberCount = \App\Models\ChapterMember::where('chapter_id', $myChapterMember->chapter_id)->where('is_active', true)->count(); $cooperativeName = \App\Models\Market\Organization::where('id', $myChapterMember->cooperative_id)->value('name'); $props['props']['chapter_info'] = [ 'chapter_name' => $myChapterMember->chapter_name, 'chapter_level' => $myChapterMember->chapter_level, 'cooperative_name' => $cooperativeName, 'member_count' => $chapterMemberCount, ]; } } ``` ### 9. VueRouteMap.php — register new pages + update existing allowedUserTypes Add to the routes array (near the cooperatives block, ~line 308): ```php '/chapter-org-chart' => [ 'component' => 'ChapterOrgChart', 'loginRequired' => true, 'allowedUserTypes'=> ['ult','super operator','operator','coordinator','coop officer','coop member'], 'module' => 'cooperatives', ], '/coop-member-search' => [ 'component' => 'CoopMemberSearch', 'loginRequired' => true, 'allowedUserTypes'=> ['ult','super operator','operator','coordinator','coop officer'], 'module' => 'cooperatives', ], '/create-coop-user' => [ 'component' => 'CreateCoopUser', 'loginRequired' => true, 'allowedUserTypes'=> ['ult','super operator','operator','coordinator','coop officer'], 'module' => 'cooperatives', ], '/assign-chapter-officer' => [ 'component' => 'AssignChapterOfficer', 'loginRequired' => true, 'allowedUserTypes'=> ['ult','super operator','operator','coordinator','coop officer'], 'module' => 'cooperatives', ], '/create-chapter' => [ 'component' => 'CreateChapter', 'loginRequired' => true, 'allowedUserTypes'=> ['ult','super operator','operator','coordinator','coop officer'], 'module' => 'cooperatives', ], '/register-chapter' => [ 'component' => 'RegisterChapter', 'loginRequired' => false, 'allowedUserTypes'=> ['*'], 'module' => 'cooperatives', ], ``` Also update existing `/cooperative-list`, `/cooperative-detail`, `/cooperative-member-register` entries to include `'coop officer'` and `'coop member'` in their `allowedUserTypes`. ### 10. useChapters.js — add new methods Append to the `useChapters()` composable return object: ```js const fetchOrgChart = async ({ chapterId = null } = {}) => { /* POST /Chapters/OrgChart */ } const fetchOfficerScope = async () => { /* POST /Chapters/Officer/Scope */ } const searchMembers = async (query) => { /* POST /Chapters/Members/Search */ } const assignOfficer = async ({ memberUserHashkey, childChapterId, role }) => { /* POST /Chapters/Officer/Assign */ } const createChapter = async ({ name, locationKey, lat = null, lng = null }) => { /* POST /Chapters/Create */ } ``` Follow same loading/error pattern as existing methods. ### 11. HomeCoopOfficer.vue — NEW `resources/js/Pages/Fragments/Home/HomeCoopOfficer.vue` Pattern: mirror `HomeCooperative.vue` structure (imports: usePageData, useNavigate, useAuth, useModal, BalanceBox, ServiceButtonGrid, SideTextButtonList, HomeSkeleton). `onMounted`: call `fetchPageData('/home-data')`. Extract `data.value?.chapter_info` for chapter name/level/hashkey/cooperative_hash. Extract `data.value?.stats` for member count, child chapters, new members 7d. Stats cards: ```js [ { title: 'Members', number: chapter_member_count, unit: 'In Chapter' }, { title: 'Sub-Chapters', number: child_chapter_count, unit: 'Direct' }, { title: 'New (7d)', number: new_members_7d, unit: 'Members' }, ] ``` Chapter badge: show chapter level (capitalize) + chapter name. e.g. "REGIONAL — Bukidnon Chapter". Services grid (icon: use FontAwesome `fas fa-*`, NOT micons for new components): ```js [ { icon: 'fas fa-sitemap', title: 'Org Chart', pagename: 'ChapterOrgChart' }, { icon: 'fas fa-search', title: 'Search Members', pagename: 'CoopMemberSearch' }, { icon: 'fas fa-user-plus', title: 'Create User', pagename: 'CreateCoopUser' }, { icon: 'fas fa-user-tie', title: 'Assign Officer', pagename: 'AssignChapterOfficer' }, { icon: 'fas fa-map-marker-alt', title: 'Create Chapter', pagename: 'CreateChapter' }, { icon: 'fas fa-share-alt', title: 'Share Invite', action: 'shareChapterLink' }, ] ``` SideTextButtonList: ```js [ { text: 'My Cooperative', action: 'viewMyCoop' }, { text: 'My Profile', pagename: 'UserInfoEdit' }, { text: 'Member Ledger', pagename: 'AccountingDashboard' }, ] ``` `shareChapterLink()` handler: ```js const shareChapterLink = () => { const coopHash = chapterInfo.value?.cooperative_hash; const chapterHash = chapterInfo.value?.chapter_hashkey; if (!coopHash || !chapterHash) return; const encoded = btoa(JSON.stringify({ coop_hash: coopHash, chapter_hash: chapterHash })); const url = `${window.location.origin}/register-chapter--e:${encoded}`; if (navigator.share) { navigator.share({ title: 'Join our cooperative chapter', url }).catch(() => {}); } else { navigator.clipboard?.writeText(url); modal.quickDismiss({ title: 'Link Copied', body: 'Registration link copied to clipboard.' }); } }; ``` No `bg-white`, `bg-light`, or `text-dark` hardcoded. Use `var(--bg-card)`, `var(--text-primary)`. Sticky header: `top: 66px; z-index: 1020;` if used. ### 12. HomeCoopMember.vue — NEW `resources/js/Pages/Fragments/Home/HomeCoopMember.vue` Fetch `/home-data`. Extract `chapter_info` (chapter_name, chapter_level, cooperative_name, member_count). Display: - Chapter name card: chapter_level (capitalized) + chapter_name + cooperative_name - Member count badge: "X members in this chapter" — count only, NO names list - Org chart strip: fetch `/Chapters/OrgChart` (own chapter's officers) → display name + role, e.g. "Pres. Juan dela Cruz". Do NOT display member names. Services grid: ```js [ { icon: 'fas fa-sitemap', title: 'Org Chart', pagename: 'ChapterOrgChart' }, { icon: 'fas fa-handshake', title: 'My Cooperative', action: 'viewMyCoop' }, { icon: 'fas fa-user-circle', title: 'My Profile', pagename: 'UserInfoEdit' }, { icon: 'fas fa-wallet', title: 'My Wallet', pagename: 'MyWallet' }, ] ``` No search button, no create user, no assign officer. ### 13. ChapterOrgChart.vue — NEW Route: `/chapter-org-chart` (no hash needed — scoped server-side to caller). `onMounted`: call `POST /Chapters/OrgChart` → get `{ own_chapter, children }`. Render: 1. **Header card**: own_chapter.name + level badge 2. **Officers section** (own chapter): flat list of `officer.name + officer.role` badges. If no officers: "No officers assigned yet." 3. **Child chapters list**: for each child in `children`: - Row: `[level badge] Chapter Name ——— X members [▶ toggle icon]` - Toggle (`showOfficers[child.id]` ref): on click, if `child.officers` is empty, call `POST /Chapters/OrgChart` with `{ chapter_id: child.id }` to fetch and populate `child.officers`. Show officer names + roles below the row. 4. COOP_MEMBER: show only own_chapter.name + officer list. No children drill-down. No member names anywhere. No `bg-white`/`bg-light`. Use theme variables. ### 14. CoopMemberSearch.vue — NEW Route: `/coop-member-search` Simple search page: - Input: text field with `v-model="query"`, debounced 400ms, min 2 chars - On each debounced change: call `POST /Chapters/Members/Search { query }` → `[{ name, role, chapter_name }]` - Results: list of cards showing `name`, `chapter_name`, role badge (if officer). NO mobile, NO hashkey link, NO other fields. - Empty state if < 2 chars: "Type at least 2 characters to search." - No results: "No members found matching '{{query}}'." ### 15. CreateCoopUser.vue — NEW Route: `/create-coop-user` `onMounted`: call `POST /Chapters/Officer/Scope` to get `{ own_chapter, cooperative, cooperative_list (if multiple) }`. If officer has multiple cooperatives → show cooperative picker dropdown first. Form fields (full user creation): - `name` (display name), `username`, `mobile_number` (format 09XXXXXXXXX), `password`, `fullname`, `firstname`, `middlename`, `lastname` (optional extras for UserInfo) - Real-time duplicate check via existing `/admin/check/mobile` and `/admin/check/username` endpoints - Chapter assignment: auto-set to officer's own chapter (read-only display: "Will be added to: [chapter_name]") - Cooperative: auto-set (read-only if single, picker if multiple) On submit: `POST /api/public/chapter/register` with `{ chapter_hash, name, username, mobile_number, password }`. On success show confirmation. Cooperative picker: use `user.value?.settings?.cooperatives` array → resolve names via `POST /Cooperatives/Get` for each hashkey → show select modal. Selected cooperative must have a chapter that matches. ### 16. AssignChapterOfficer.vue — NEW Route: `/assign-chapter-officer` `onMounted`: call `POST /Chapters/Officer/Scope` → get `eligible_members` (members of own chapter, not yet officers) + `child_chapters`. Step 1 — pick member: searchable list of `eligible_members` (name only). Tap to select. Step 2 — pick child chapter: list of `child_chapters` from scope. Tap to select. Step 3 — pick role: dropdown `[PRESIDENT, VICE_PRESIDENT, SECRETARY, TREASURER, AUDITOR, BOARD_MEMBER]` Step 4 — confirm: "Assign [name] as [role] to [child_chapter_name]? This will MOVE them from [own_chapter_name] to [child_chapter_name]." On confirm: `POST /Chapters/Officer/Assign { member_user_hashkey, child_chapter_id, role }`. On success: navigate back to home. ### 17. CreateChapter.vue — NEW Route: `/create-chapter` `onMounted`: call `POST /Chapters/Officer/Scope` → get own_chapter (name, level). Compute `childLevel` from CHILD_LEVELS map. If own_chapter.level === 'barangay' → show error "Cannot create sub-chapters at barangay level." Form: - `name` (string, required) - `location_key` (auto-lowercase from name, editable) - `lat`, `lng` (optional) - Read-only: "Level: [childLevel]", "Parent: [own_chapter.name]", "Cooperative: [cooperative.name]" On submit: `POST /Chapters/Create { name, location_key, lat, lng }`. On success: show confirmation with new chapter name + level badge. ### 18. RegisterChapter.vue — NEW (public page) Route: `/register-chapter--e:` (no auth required — `loginRequired: false`) `onMounted`: 1. Decode `target` prop (base64 JSON: `{ coop_hash, chapter_hash }`) 2. Call `GET /api/public/chapter/{chapter_hash}` → show chapter name, level, cooperative name Form: - `name`, `username`, `mobile_number`, `password` - Real-time duplicate checks via existing public endpoints - On submit: `POST /api/public/chapter/register { chapter_hash, name, username, mobile_number, password }` - On success: show "Welcome to [cooperative name] — [chapter name]! Your account has been created. Please login." with Login button. No auth required. No sidebar/header — full-page public layout (mirror `RegisterCoop.vue` structure). ## context ### home-data closure location (routes/web.php lines 101-199) COORDINATOR branch at line 147. Insert COOP_OFFICER and COOP_MEMBER blocks after line 153 (after end of coordinator if block). `$acctType` already resolved at line 117. Use `\App\Enums\UserTypes::COOP_OFFICER` and `\App\Enums\UserTypes::COOP_MEMBER`. ### ChapterController existing methods (lines 27, 75, 108, 141, 194) - `hierarchy()` line 27 — GET chapters by parent - `mapData()` line 75 — lat/lng dots - `members()` line 108 — members for chapter_id (returns full user info — DO NOT reuse for search, too much data) - `assignMember()` line 141 — generic assign (does NOT move, does NOT upgrade acct_type — new `assignOfficer()` must handle move + upgrade) - `removeMember()` line 194 — remove by hashkey ### CooperativeController::publicRegisterMember (lines 410-460) Creates user with `acct_type = 'user'`, no chapter assignment. The new `publicRegisterToChapter` in ChapterController is the chapter-aware equivalent. ### useChapters.js existing methods (lines 8-127) Returns: fetchHierarchy, fetchMapData, fetchOrgHierarchy, fetchOrgMapData, fetchMembers, assignMember, removeMember, fetchPositions, syncAutoAssignments. ADD: fetchOrgChart (POST /Chapters/OrgChart), fetchOfficerScope (POST /Chapters/Officer/Scope), searchMembers (POST /Chapters/Members/Search), assignOfficer (POST /Chapters/Officer/Assign), createChapter (POST /Chapters/Create). ### RegisterCoop.vue (existing public page — mirror for RegisterChapter.vue) File: `resources/js/Pages/RegisterCoop.vue`. Route: `/register-coop--h:HASHKEY`. No auth. Calls `GET /api/public/cooperative/{hkey}` then `POST /api/public/cooperative/register`. Mirror this structure for RegisterChapter.vue. ### Share link encoding Payload: `btoa(JSON.stringify({ coop_hash, chapter_hash }))`. Frontend decodes with `JSON.parse(atob(target))` where `target` is the prop injected by VueRouteMap from the `--e:` URL segment. This follows the existing encoded payload pattern already used in VueRouteMap. ### Web Share API pattern (already in CooperativeDetail.vue) ```js if (navigator.share) { navigator.share({ title, url }).catch(() => {}); } else { navigator.clipboard?.writeText(url); // show copied notice } ``` ### LEVEL_ORDER for PHP childLevel lookup ```php const CHILD_LEVELS = [ 'national' => ['region'], 'region' => ['province'], 'province' => ['city', 'municipal'], 'city' => ['barangay'], 'municipal' => ['barangay'], 'barangay' => [], ]; ``` Helper: `$childLevels = CHILD_LEVELS[$chapter->level] ?? []; if (empty($childLevels)) { return 422; }` ### Hypervel migration imports (for any new migration if needed) ```php use Hyperf\Database\Schema\Blueprint; use Hypervel\Database\Migrations\Migration; use Hypervel\Support\Facades\Schema; ``` NO Illuminate classes. ### UserActions in $RoleswithNoTargetUser `ViewChapterOrgChart`, `ManageChapterMembers`, `ViewScopedMemberReports`, `AssignChapterOfficer` must be in `UserPermissions::$RoleswithNoTargetUser` (added by prerequisite plan). Verify before adding routes. ## notes - dictionary: ai-docs/dictionary.md - linters: none detected - constraints: - PREREQUISITE: execute plan 4fc3b455fb62b15dfb06790a1f421c96 first (adds COOP_OFFICER/COOP_MEMBER types, migrations, permissions, Home.vue fragments) - All chapter level checks use in_array(level, ['city','municipal']) not equality — city and municipal are peers - memberSearch NEVER returns mobile_number, hashkey, username or any field other than name/role/chapter_name - HomeCoopMember NEVER shows fellow member names — member count only; officer names ARE shown in org chart strip - assignOfficer MOVES the member (delete parent row, insert child row) — not a copy - createChapter links to officer's cooperative via cooperative_id — chapters are coop-scoped - share link uses encoded payload (btoa JSON) not hashkey — decode in RegisterChapter.vue onMounted - No bg-white, bg-light, text-dark in any new Vue component — use var(--bg-card), var(--text-primary) - Sticky headers must use top: 66px; z-index: 1020 - Raw DB queries must include created_by/updated_by (Auth::id()) and timestamps (now()) - All new routes use Hypervel-style Route::post(..., ..., ['middleware' => 'auth'])