Skip to content

Announcements

Overview

The Announcements System provides workspace-level announcement management for administrators. It allows creating, editing, and managing announcements that are displayed to users through the notification system. Announcements support rich content, targeting, read/unread tracking, and can be displayed in the notification bell component.

Architecture

System Components

  1. Announcement Management: Create, edit, delete announcements (admin only)
  2. Announcement Display: Show announcements in notification bell and dedicated pages
  3. Announcement Table: List and manage announcements with sorting, filtering, and pagination
  4. Read/Unread Tracking: Track which users have read announcements
  5. Permission Management: Access control for announcement management

File Structure

Core Announcement Components

Vue Component Files (.vue)

  • components/dam/Dialogs/Org-Settings/AnnouncementDialog.vue - Announcement dialog component

    • Create new announcement
    • Edit existing announcement
    • View announcement details
    • Rich content editor
    • Targeting options
    • Publish/unpublish toggle
  • components/dam/table/announcementTable.vue - Announcement table component

    • Display announcements list
    • Sorting functionality
    • Filtering options
    • Pagination
    • Bulk actions
    • Edit/delete actions

Announcement Display & Integration

Vue Component Files (.vue)

  • components/theme/global/DamNotification.vue - Main notification component

    • Includes announcements tab
    • Display announcements in notification bell
    • Mark as read functionality
    • Announcement actions
  • components/dam/Collage/AvatarList.vue - Avatar list component

    • May display announcement publisher avatars
    • User avatars for announcement creators

Announcement Management Pages

Vue Page Files (.vue)

  • pages/_workspace_id/workspace-settings/dam/_instance_id/announcements/list.vue - Announcements list and management page
    • Admin-only access
    • Announcements list display
    • Create/edit announcements
    • Delete announcements
    • Filter and search
    • Pagination

Route: /:workspace_id/workspace-settings/dam/:instance_id/announcements/list

Announcement Integration in Navigation

Vue Component Files (.vue)

  • components/theme/global/LeftMenu.vue - Left menu component

    • May include announcements access
    • Navigation link to announcements
  • components/theme/settings/AccountSettingsLeftMenu.vue - Account settings menu

    • May include announcements link
    • Navigation to announcements management

Supporting Files

JavaScript Files (.js)

  • plugins/helper.js - Helper functions plugin

    • canViewAnnouncements permission check
    • canManageAnnouncements permission check
    • Announcement utility functions
  • middleware/can-access-dam-module.js - DAM module access middleware

    • May check announcement permissions
    • Access control for announcement features

Announcement Structure

Announcement Object

javascript
{
  id: 1,
  workspace_id: 123,
  instance_id: 456,
  title: "New Feature Release",
  content: "We're excited to announce new features...",
  content_type: "html", // "text" or "html"
  priority: "high", // "low", "medium", "high"
  target_audience: "all", // "all", "specific_roles", "specific_users"
  target_roles: [], // Array of role IDs if target_audience is "specific_roles"
  target_users: [], // Array of user IDs if target_audience is "specific_users"
  is_published: true,
  publish_at: "2024-01-15T10:00:00Z",
  expires_at: null, // Optional expiration date
  created_by: 789,
  created_at: "2024-01-15T10:00:00Z",
  updated_at: "2024-01-20T14:30:00Z",
  read_count: 45,
  total_users: 100
}

Announcement Read Status Object

javascript
{
  id: 1,
  announcement_id: 1,
  user_id: 456,
  is_read: true,
  read_at: "2024-01-16T09:00:00Z",
  created_at: "2024-01-15T10:00:00Z"
}

Component Implementation

Announcement Dialog Component

File: components/dam/Dialogs/Org-Settings/AnnouncementDialog.vue

Dialog for creating, editing, and viewing announcements.

Features

  • Create new announcement
  • Edit existing announcement
  • View announcement details
  • Rich content editor (HTML/text)
  • Title and content fields
  • Priority selection
  • Target audience selection
  • Publish/unpublish toggle
  • Publish date/time
  • Expiration date (optional)

Props

javascript
{
  dialog: {
    type: Boolean,
    default: false
  },
  announcement: {
    type: Object,
    default: null
    // Announcement object to edit (null for create)
  },
  workspaceId: {
    type: [String, Number],
    required: true
  },
  instanceId: {
    type: [String, Number],
    required: true
  }
}

Data

javascript
data() {
  return {
    form: {
      title: '',
      content: '',
      content_type: 'text', // 'text' or 'html'
      priority: 'medium', // 'low', 'medium', 'high'
      target_audience: 'all', // 'all', 'specific_roles', 'specific_users'
      target_roles: [],
      target_users: [],
      is_published: false,
      publish_at: null,
      expires_at: null
    },
    contentEditor: null,
    loading: false
  }
}

Methods

javascript
// Load announcement data for editing
loadAnnouncement() {
  if (this.announcement) {
    this.form = {
      title: this.announcement.title,
      content: this.announcement.content,
      content_type: this.announcement.content_type || 'text',
      priority: this.announcement.priority || 'medium',
      target_audience: this.announcement.target_audience || 'all',
      target_roles: this.announcement.target_roles || [],
      target_users: this.announcement.target_users || [],
      is_published: this.announcement.is_published || false,
      publish_at: this.announcement.publish_at,
      expires_at: this.announcement.expires_at
    }
  }
}

// Validate form
validateForm() {
  if (!this.form.title.trim()) {
    this.$toast.error('Title is required')
    return false
  }
  if (!this.form.content.trim()) {
    this.$toast.error('Content is required')
    return false
  }
  if (this.form.target_audience === 'specific_roles' && this.form.target_roles.length === 0) {
    this.$toast.error('Please select at least one role')
    return false
  }
  if (this.form.target_audience === 'specific_users' && this.form.target_users.length === 0) {
    this.$toast.error('Please select at least one user')
    return false
  }
  return true
}

// Save announcement
async saveAnnouncement() {
  if (!this.validateForm()) {
    return
  }
  
  this.loading = true
  try {
    const payload = {
      workspace_id: this.workspaceId,
      instance_id: this.instanceId,
      ...this.form
    }
    
    if (this.announcement) {
      // Update existing
      const response = await this.$axios.put(
        `/announcement/update`,
        { id: this.announcement.id, ...payload }
      )
      this.$toast.success('Announcement updated successfully')
      this.$emit('saved', response.data.announcement)
    } else {
      // Create new
      const response = await this.$axios.post(
        `/announcement/add`,
        payload
      )
      this.$toast.success('Announcement created successfully')
      this.$emit('saved', response.data.announcement)
    }
    
    this.closeDialog()
  } catch (error) {
    this.$toast.error('Failed to save announcement')
  } finally {
    this.loading = false
  }
}

// Close dialog
closeDialog() {
  this.$emit('close')
  this.resetForm()
}

// Reset form
resetForm() {
  this.form = {
    title: '',
    content: '',
    content_type: 'text',
    priority: 'medium',
    target_audience: 'all',
    target_roles: [],
    target_users: [],
    is_published: false,
    publish_at: null,
    expires_at: null
  }
}

Usage

vue
<template>
  <div>
    <v-btn @click="openCreateDialog">Create Announcement</v-btn>
    
    <AnnouncementDialog
      :dialog="dialogOpen"
      :announcement="selectedAnnouncement"
      :workspace-id="workspaceId"
      :instance-id="instanceId"
      @close="closeDialog"
      @saved="handleAnnouncementSaved"
    />
  </div>
</template>

<script>
import AnnouncementDialog from '~/components/dam/Dialogs/Org-Settings/AnnouncementDialog.vue'

export default {
  components: {
    AnnouncementDialog
  },
  data() {
    return {
      dialogOpen: false,
      selectedAnnouncement: null,
      workspaceId: this.$route.params.workspace_id,
      instanceId: this.$route.params.instance_id
    }
  },
  methods: {
    openCreateDialog() {
      this.selectedAnnouncement = null
      this.dialogOpen = true
    },
    editAnnouncement(announcement) {
      this.selectedAnnouncement = announcement
      this.dialogOpen = true
    },
    closeDialog() {
      this.dialogOpen = false
      this.selectedAnnouncement = null
    },
    handleAnnouncementSaved(announcement) {
      // Refresh announcements list
      this.loadAnnouncements()
    }
  }
}
</script>

Announcement Table Component

File: components/dam/table/announcementTable.vue

Table component for displaying and managing announcements list.

Features

  • Display announcements in table format
  • Sorting by column (title, priority, created_at, etc.)
  • Filtering options
  • Search functionality
  • Pagination
  • Bulk actions (delete, publish/unpublish)
  • Edit/delete actions per row
  • Read statistics display

Props

javascript
{
  announcements: {
    type: Array,
    required: true
    // Array of announcement objects
  },
  loading: {
    type: Boolean,
    default: false
  },
  total: {
    type: Number,
    default: 0
  },
  page: {
    type: Number,
    default: 1
  },
  perPage: {
    type: Number,
    default: 20
  }
}

Methods

javascript
// Sort announcements
sortBy(column) {
  // Toggle sort direction
  // Update sort state
  // Emit sort event
}

// Filter announcements
applyFilter(filter) {
  // Apply filter
  // Emit filter event
}

// Search announcements
searchAnnouncements(query) {
  // Search in title and content
  // Emit search event
}

// Delete announcement
async deleteAnnouncement(announcementId) {
  // Confirm deletion
  // Call delete API
  // Remove from list
  // Show success message
}

// Toggle publish status
async togglePublish(announcement) {
  // Toggle is_published
  // Call update API
  // Update local state
}

Usage

vue
<template>
  <div>
    <AnnouncementTable
      :announcements="announcements"
      :loading="loading"
      :total="total"
      :page="currentPage"
      :per-page="perPage"
      @sort="handleSort"
      @filter="handleFilter"
      @search="handleSearch"
      @edit="editAnnouncement"
      @delete="deleteAnnouncement"
      @page-change="handlePageChange"
    />
  </div>
</template>

<script>
import AnnouncementTable from '~/components/dam/table/announcementTable.vue'

export default {
  components: {
    AnnouncementTable
  },
  data() {
    return {
      announcements: [],
      loading: false,
      total: 0,
      currentPage: 1,
      perPage: 20
    }
  },
  methods: {
    handleSort(sortData) {
      // Handle sorting
      this.loadAnnouncements(sortData)
    },
    handleFilter(filter) {
      // Handle filtering
      this.loadAnnouncements({ filter })
    },
    handleSearch(query) {
      // Handle search
      this.loadAnnouncements({ search: query })
    },
    editAnnouncement(announcement) {
      // Open edit dialog
    },
    deleteAnnouncement(announcementId) {
      // Delete announcement
    }
  }
}
</script>

Announcements List Page

File: pages/_workspace_id/workspace-settings/dam/_instance_id/announcements/list.vue

Route: /:workspace_id/workspace-settings/dam/:instance_id/announcements/list

Main announcements management page (admin only).

Features

  • Announcements list display
  • Create new announcement
  • Edit existing announcement
  • Delete announcement
  • Filter and search
  • Sorting
  • Pagination
  • Bulk actions

Page Structure

vue
<template>
  <div class="announcements-page">
    <v-card>
      <v-card-title>
        Announcements
        <v-spacer />
        <v-btn
          v-if="canManageAnnouncements"
          color="primary"
          @click="openCreateDialog"
        >
          <AddIcon /> Create Announcement
        </v-btn>
      </v-card-title>
      
      <v-card-text>
        <!-- Search and Filters -->
        <v-row>
          <v-col cols="12" md="6">
            <v-text-field
              v-model="searchQuery"
              placeholder="Search announcements..."
              prepend-inner-icon="mdi-magnify"
              @input="handleSearch"
            />
          </v-col>
          <v-col cols="12" md="6">
            <v-select
              v-model="selectedPriority"
              :items="priorityOptions"
              label="Filter by Priority"
              clearable
              @change="handleFilter"
            />
          </v-col>
        </v-row>
        
        <!-- Announcements Table -->
        <AnnouncementTable
          :announcements="announcements"
          :loading="loading"
          :total="total"
          :page="currentPage"
          :per-page="perPage"
          @sort="handleSort"
          @filter="handleFilter"
          @search="handleSearch"
          @edit="editAnnouncement"
          @delete="deleteAnnouncement"
          @page-change="handlePageChange"
        />
      </v-card-text>
    </v-card>
    
    <!-- Create/Edit Dialog -->
    <AnnouncementDialog
      :dialog="dialogOpen"
      :announcement="selectedAnnouncement"
      :workspace-id="workspaceId"
      :instance-id="instanceId"
      @close="closeDialog"
      @saved="handleAnnouncementSaved"
    />
  </div>
</template>

Announcement Display in Notifications

File: components/theme/global/DamNotification.vue

Announcements are displayed in the notification bell component.

Features

  • Announcements tab in notification bell
  • Display published announcements
  • Mark as read functionality
  • Filter by read/unread
  • Click to view full announcement

Integration

vue
<template>
  <v-tabs v-model="activeTab">
    <v-tab value="system">System</v-tab>
    <v-tab value="announcements">
      Announcements
      <v-chip
        v-if="unreadAnnouncementsCount > 0"
        small
        color="error"
        class="ml-2"
      >
        {{ unreadAnnouncementsCount }}
      </v-chip>
    </v-tab>
  </v-tabs>
  
  <v-tabs-items v-model="activeTab">
    <v-tab-item value="announcements">
      <v-list>
        <v-list-item
          v-for="announcement in announcements"
          :key="announcement.id"
          :class="{ 'unread': !announcement.is_read }"
          @click="viewAnnouncement(announcement)"
        >
          <v-list-item-content>
            <v-list-item-title>{{ announcement.title }}</v-list-item-title>
            <v-list-item-subtitle>
              {{ formatDate(announcement.created_at) }}
            </v-list-item-subtitle>
          </v-list-item-content>
          <v-list-item-action>
            <v-btn
              v-if="!announcement.is_read"
              icon
              small
              @click.stop="markAsRead(announcement.id)"
            >
              <v-icon small>mdi-check</v-icon>
            </v-btn>
          </v-list-item-action>
        </v-list-item>
      </v-list>
    </v-tab-item>
  </v-tabs-items>
</template>

API Integration

Get Announcements List

Endpoint: GET /announcement/list

Query Parameters:

  • workspace_id (required) - Workspace identifier
  • instance_id (required) - Instance identifier
  • page (optional) - Page number (default: 1)
  • per_page (optional) - Items per page (default: 20)
  • sort_by (optional) - Sort column (default: "created_at")
  • sort_order (optional) - Sort order "asc" or "desc" (default: "desc")
  • search (optional) - Search query
  • priority (optional) - Filter by priority
  • is_published (optional) - Filter by published status

Response:

json
{
  "announcements": [
    {
      "id": 1,
      "workspace_id": 123,
      "instance_id": 456,
      "title": "New Feature Release",
      "content": "We're excited to announce new features...",
      "content_type": "html",
      "priority": "high",
      "target_audience": "all",
      "target_roles": [],
      "target_users": [],
      "is_published": true,
      "publish_at": "2024-01-15T10:00:00Z",
      "expires_at": null,
      "created_by": 789,
      "created_at": "2024-01-15T10:00:00Z",
      "updated_at": "2024-01-20T14:30:00Z",
      "read_count": 45,
      "total_users": 100
    }
  ],
  "pagination": {
    "current_page": 1,
    "per_page": 20,
    "total": 15,
    "total_pages": 1
  }
}

Create Announcement

Endpoint: POST /announcement/add

Request Body:

json
{
  "workspace_id": 123,
  "instance_id": 456,
  "title": "New Feature Release",
  "content": "We're excited to announce new features...",
  "content_type": "html",
  "priority": "high",
  "target_audience": "all",
  "target_roles": [],
  "target_users": [],
  "is_published": true,
  "publish_at": "2024-01-15T10:00:00Z",
  "expires_at": null
}

Response:

json
{
  "success": true,
  "announcement": {
    "id": 1,
    "workspace_id": 123,
    "instance_id": 456,
    "title": "New Feature Release",
    "content": "We're excited to announce new features...",
    "content_type": "html",
    "priority": "high",
    "target_audience": "all",
    "is_published": true,
    "publish_at": "2024-01-15T10:00:00Z",
    "created_by": 789,
    "created_at": "2024-01-15T10:00:00Z"
  }
}

Update Announcement

Endpoint: PUT /announcement/update

Request Body:

json
{
  "id": 1,
  "title": "Updated Feature Release",
  "content": "Updated content...",
  "content_type": "html",
  "priority": "medium",
  "is_published": true
}

Response:

json
{
  "success": true,
  "announcement": {
    "id": 1,
    "title": "Updated Feature Release",
    "content": "Updated content...",
    "updated_at": "2024-01-20T14:30:00Z"
  }
}

Delete Announcement

Endpoint: DELETE /announcement/delete

Query Parameters:

  • id (required) - Announcement ID

Response:

json
{
  "success": true,
  "message": "Announcement deleted successfully"
}

Get Announcements for Notifications

Endpoint: GET /announcement/notification-list

Query Parameters:

  • workspace_id (required) - Workspace identifier
  • user_id (required) - User identifier

Response:

json
{
  "announcements": [
    {
      "id": 1,
      "title": "New Feature Release",
      "content": "We're excited to announce new features...",
      "priority": "high",
      "is_read": false,
      "created_at": "2024-01-15T10:00:00Z"
    }
  ],
  "unread_count": 3
}

Mark Announcement as Read/Unread

Endpoint: POST /announcement/read-unread

Request Body:

json
{
  "announcement_id": 1,
  "user_id": 456,
  "is_read": true
}

Response:

json
{
  "success": true,
  "message": "Announcement marked as read"
}

Mark All Announcements as Read

Endpoint: POST /announcement/mark-all-read

Request Body:

json
{
  "workspace_id": 123,
  "user_id": 456
}

Response:

json
{
  "success": true,
  "message": "All announcements marked as read",
  "marked_count": 5
}

Workflows

Create Announcement

1. Admin navigates to announcements page
   Route: /:workspace_id/workspace-settings/dam/:instance_id/announcements/list

2. Click "Create Announcement"
   Component: AnnouncementDialog.vue opens

3. Fill announcement form
   - Enter title
   - Enter content (text or HTML)
   - Select priority
   - Select target audience
   - Set publish date (optional)
   - Set expiration date (optional)

4. Validate form
   - Check required fields
   - Validate target audience selection

5. Save announcement
   API: POST /announcement/add
   Body: { workspace_id, instance_id, title, content, ... }

6. Announcement created
   - Saved to database
   - If published, visible to users
   - Appears in announcements list

7. Update UI
   - Refresh announcements list
   - Show success message
   - Close dialog

Display Announcements to Users

1. User clicks notification bell
   Component: DamNotification.vue

2. Switch to "Announcements" tab
   - Shows announcements tab
   - Displays unread count badge

3. Load announcements
   API: GET /announcement/notification-list
   Query: { workspace_id, user_id }

4. Display announcements
   - Show published announcements
   - Filter by target audience
   - Highlight unread announcements
   - Show priority indicators

5. User interacts
   - Click announcement → View full content
   - Mark as read → Update read status
   - Mark all as read → Update all read statuses

Mark Announcement as Read

1. User views announcement
   Component: DamNotification.vue
   - Click announcement in list
   OR
   - Click "Mark as read" button

2. Call API
   API: POST /announcement/read-unread
   Body: { announcement_id, user_id, is_read: true }

3. Update read status
   - Set is_read = true
   - Set read_at timestamp
   - Decrement unread count

4. Update UI
   - Remove unread styling
   - Update badge count
   - Refresh announcement list

Edit Announcement

1. Admin navigates to announcements list
   Route: /:workspace_id/workspace-settings/dam/:instance_id/announcements/list

2. Click "Edit" on announcement
   Component: AnnouncementDialog.vue opens with announcement data

3. Modify announcement
   - Update title, content, priority, etc.
   - Change target audience
   - Toggle publish status

4. Save changes
   API: PUT /announcement/update
   Body: { id, title, content, ... }

5. Announcement updated
   - Changes saved to database
   - If published, updates visible to users

6. Update UI
   - Refresh announcements list
   - Show success message
   - Close dialog

Delete Announcement

1. Admin navigates to announcements list
   Route: /:workspace_id/workspace-settings/dam/:instance_id/announcements/list

2. Click "Delete" on announcement
   - Confirm deletion dialog appears

3. Confirm deletion
   - User confirms deletion

4. Call API
   API: DELETE /announcement/delete
   Query: { id: announcementId }

5. Announcement deleted
   - Removed from database
   - No longer visible to users

6. Update UI
   - Remove from announcements list
   - Show success message
   - Refresh list

Component Integration

Using Announcements in Notifications

vue
<template>
  <DamNotification
    :workspace-id="workspaceId"
    :user-id="userId"
  />
</template>

<script>
import DamNotification from '~/components/theme/global/DamNotification.vue'

export default {
  components: {
    DamNotification
  },
  computed: {
    workspaceId() {
      return this.$route.params.workspace_id
    },
    userId() {
      return this.$store.state.auth.user.id
    }
  }
}
</script>

Using Announcements List Page

vue
<template>
  <div class="announcements-list-page">
    <AnnouncementTable
      :announcements="announcements"
      :loading="loading"
      :total="total"
      :page="currentPage"
      @edit="editAnnouncement"
      @delete="deleteAnnouncement"
    />
    
    <AnnouncementDialog
      :dialog="dialogOpen"
      :announcement="selectedAnnouncement"
      :workspace-id="workspaceId"
      :instance-id="instanceId"
      @close="closeDialog"
      @saved="handleSaved"
    />
  </div>
</template>

<script>
import AnnouncementTable from '~/components/dam/table/announcementTable.vue'
import AnnouncementDialog from '~/components/dam/Dialogs/Org-Settings/AnnouncementDialog.vue'

export default {
  components: {
    AnnouncementTable,
    AnnouncementDialog
  },
  data() {
    return {
      announcements: [],
      loading: false,
      total: 0,
      currentPage: 1,
      dialogOpen: false,
      selectedAnnouncement: null,
      workspaceId: this.$route.params.workspace_id,
      instanceId: this.$route.params.instance_id
    }
  },
  async mounted() {
    await this.loadAnnouncements()
  },
  methods: {
    async loadAnnouncements() {
      this.loading = true
      try {
        const response = await this.$axios.get('/announcement/list', {
          params: {
            workspace_id: this.workspaceId,
            instance_id: this.instanceId,
            page: this.currentPage,
            per_page: 20
          }
        })
        this.announcements = response.data.announcements
        this.total = response.data.pagination.total
      } catch (error) {
        this.$toast.error('Failed to load announcements')
      } finally {
        this.loading = false
      }
    },
    editAnnouncement(announcement) {
      this.selectedAnnouncement = announcement
      this.dialogOpen = true
    },
    deleteAnnouncement(announcementId) {
      // Confirm and delete
    },
    closeDialog() {
      this.dialogOpen = false
      this.selectedAnnouncement = null
    },
    handleSaved() {
      this.loadAnnouncements()
    }
  }
}
</script>

Permission Management

Can View Announcements

File: plugins/helper.js

javascript
// Check if user can view announcements
export function canViewAnnouncements() {
  const user = this.$store.state.auth.user
  const permissions = this.$store.state.auth.permissions
  
  // All authenticated users can view announcements
  return user && user.id
}

Can Manage Announcements

File: plugins/helper.js

javascript
// Check if user can manage announcements
export function canManageAnnouncements() {
  const user = this.$store.state.auth.user
  const permissions = this.$store.state.auth.permissions
  
  // Check for announcement management permission
  return permissions.includes('can-manage-announcements') ||
         user.role === 'admin' ||
         user.role === 'workspace_admin'
}

Usage

vue
<script>
import { canManageAnnouncements } from '~/plugins/helper'

export default {
  async beforeRouteEnter(to, from, next) {
    const canManage = await canManageAnnouncements.call(this)
    if (!canManage) {
      next({ name: 'forbidden' })
    } else {
      next()
    }
  },
  computed: {
    canManage() {
      return canManageAnnouncements.call(this)
    }
  }
}
</script>