Skip to main content

Overview

Recommendation actions manage maintenance recommendations with severity levels, attachments, and analytics. All functions are located in src/app/actions/recommendations.ts.

Recommendation Operations

getRecommendationsService

Retrieves a list of recommendations with optional filtering.
params
object
required
Query parameters for filtering (at least one parameter required)
[key: string]
string | string[]
Filter parameters (e.g., site_id, severity, status)
data
Recommendation[]
Array of recommendation objects (returns empty array on error)
"use server";
import { getRecommendationsService } from "@/app/actions";

export default async function RecommendationsPage() {
  const recommendations = await getRecommendationsService({});

  return <RecommendationTable data={recommendations} />;
}
This function returns an empty array on permission errors (403) or network issues instead of throwing. No try-catch needed.

getRecommendationService

Retrieves a single recommendation by ID.
id
string
required
Unique recommendation identifier
data
Recommendation
Complete recommendation object with attachments and relationships
import { getRecommendationService } from "@/app/actions";

const recommendation = await getRecommendationService("rec-123");

console.log(recommendation.title);
console.log(recommendation.severity);
console.log(recommendation.attachments);
console.log(recommendation.site.name);

addRecommendationService

Creates a new maintenance recommendation.
payload
AddRecommendationPayload
required
Recommendation creation data
title
string
required
Recommendation title
severity
'low' | 'medium' | 'high' | 'critical'
required
Severity level
description
string
required
Detailed description of the recommendation
attachments
RecommendationAttachment[]
Array of file attachments
type
string
required
Attachment type (e.g., “document”, “image”)
url
string
required
File URL
name
string
required
File name
site
object
required
id
string
required
Site ID
asset
object
required
id
string
required
Asset ID
sampling_point
object
required
id
string
required
Sampling point ID
recommender
object
required
id
string
required
User ID of recommender
response
ApiResponse
Standard API response object
success
boolean
Whether the operation succeeded
statusCode
number
HTTP status code
message
string
Response message
data
object
Created recommendation data
"use client";
import { addRecommendationService } from "@/app/actions";
import { AddRecommendationPayload } from "@/schema";
import { toast } from "sonner";

export function AddRecommendationForm() {
  const handleSubmit = async (data: AddRecommendationPayload) => {
    const payload: AddRecommendationPayload = {
      title: data.title,
      severity: data.severity,
      description: data.description,
      site: { id: data.site.id },
      asset: { id: data.asset.id },
      sampling_point: { id: data.sampling_point.id },
      recommender: { id: data.recommender.id },
      attachments: [],
    };

    const response = await addRecommendationService(payload);
    
    if (response.success) {
      toast.success("Recommendation created successfully");
      router.push("/recommendations");
    } else {
      toast.error(response.message || "Failed to create recommendation");
    }
  };

  return <form onSubmit={handleSubmit}>...</form>;
}
The payload is wrapped in a { recommendation: payload } object before being sent to the API. This is handled automatically by the service.

editRecommendationService

Updates an existing recommendation.
id
string
required
Recommendation ID to update
payload
EditRecommendationPayload
required
Updated recommendation data (same structure as AddRecommendationPayload)
response
ApiResponse
Standard API response object
import { editRecommendationService } from "@/app/actions";
import { EditRecommendationPayload } from "@/schema";
import { toast } from "sonner";

const handleUpdate = async (
  recId: string, 
  data: EditRecommendationPayload
) => {
  const response = await editRecommendationService(recId, data);
  
  if (response.success) {
    toast.success("Recommendation updated successfully");
  } else {
    toast.error(response.message);
  }
};

deleteRecommendationService

Deletes a recommendation.
id
string
required
Recommendation ID to delete
response
ApiResponse
Standard API response object
import { deleteRecommendationService } from "@/app/actions";
import { toast } from "sonner";

const handleDelete = async (recId: string) => {
  const response = await deleteRecommendationService(recId);
  
  if (response.success) {
    toast.success("Recommendation deleted successfully");
  } else {
    toast.error(response.message);
  }
};

Analytics Operations

getRecommendationAnalyticsService

Retrieves recommendation analytics for dashboard metrics and trend analysis.
data
RecommendationAnalytics[]
Array of monthly recommendation analytics (returns empty array on error)
month
string
Month identifier (e.g., “2024-01”)
total_count
number
Total recommendations for the month
total_trend_percentage
number
Percentage change from previous month
overdue_count
number
Number of overdue recommendations
overdue_trend_percentage
number
Overdue trend percentage
open_overdue_count
number
Number of open and overdue recommendations
open_overdue_trend_percentage
number
Open/overdue trend percentage
import { getRecommendationAnalyticsService } from "@/app/actions";

export default async function RecommendationsDashboard() {
  const analytics = await getRecommendationAnalyticsService();

  if (analytics.length === 0) {
    return <div>No analytics data available</div>;
  }

  const currentMonth = analytics[analytics.length - 1];

  return (
    <div>
      <MetricCard 
        title="Total Recommendations" 
        value={currentMonth.total_count}
        trend={currentMonth.total_trend_percentage}
      />
      <MetricCard 
        title="Overdue" 
        value={currentMonth.overdue_count}
        trend={currentMonth.overdue_trend_percentage}
      />
      <MetricCard 
        title="Open & Overdue" 
        value={currentMonth.open_overdue_count}
        trend={currentMonth.open_overdue_trend_percentage}
      />
    </div>
  );
}
Returns an empty array on permission errors (403), non-JSON responses, or network issues instead of throwing. Check array length before accessing data.

Common Workflows

Create Recommendation from Sample Analysis

import { 
  getSampleService, 
  addRecommendationService,
  getAssetService 
} from "@/app/actions";
import { toast } from "sonner";

// 1. Get sample data
const sample = await getSampleService(sampleId);

// 2. Analyze severity
let severity: "low" | "medium" | "high" | "critical" = "low";
let title = "";
let description = "";

if (sample.severity === "Critical") {
  severity = "critical";
  title = "Immediate action required - Critical sample results";
  description = `Critical wear metals detected in sample ${sample.serial_number}.`;
} else if (sample.severity === "Warning") {
  severity = "high";
  title = "Warning - Elevated wear metals";
  description = `Warning levels detected in sample ${sample.serial_number}.`;
}

// 3. Create recommendation
const response = await addRecommendationService({
  title,
  severity,
  description,
  site: { id: sample.site.id },
  asset: { id: sample.asset.id },
  sampling_point: { id: sample.sampling_point.id },
  recommender: { id: currentUser.id },
});

if (response.success) {
  toast.success("Recommendation created from sample analysis");
}
import { 
  getAlarmService, 
  editAlarmService,
  addRecommendationService 
} from "@/app/actions";

// 1. Get alarm
const alarm = await getAlarmService(alarmId);

// 2. Create recommendation
const recResponse = await addRecommendationService({
  title: `Address alarm: ${alarm.parameter}`,
  severity: "high",
  description: `Recommendation for alarm detected on ${alarm.first_detected}`,
  site: { id: alarm.site.id },
  asset: { id: assetId },
  sampling_point: { id: samplingPointId },
  recommender: { id: currentUser.id },
});

if (!recResponse.success) {
  toast.error("Failed to create recommendation");
  return;
}

// 3. Link to alarm
const recId = recResponse.data?.data?.id;
if (recId) {
  const linkResponse = await editAlarmService(alarmId, {
    ...alarm,
    linked_recommendations: [
      ...alarm.linked_recommendations,
      { id: recId },
    ],
  });
  
  if (linkResponse.success) {
    toast.success("Recommendation created and linked to alarm");
  }
}

Severity-based Filtering

import { getRecommendationsService } from "@/app/actions";

// Get critical and high severity recommendations
const [critical, high] = await Promise.all([
  getRecommendationsService({ severity: "critical" }),
  getRecommendationsService({ severity: "high" }),
]);

const urgentRecommendations = [...critical, ...high];

console.log(`${urgentRecommendations.length} urgent recommendations`);

Type Definitions

Import types from @/types and schemas from @/schema:
import { 
  Recommendation, 
  RecommendationAttachment,
  RecommendationAnalytics 
} from "@/types";
import { 
  AddRecommendationPayload, 
  EditRecommendationPayload, 
  ADD_RECOMMENDATION_SCHEMA 
} from "@/schema";
import { ApiResponse } from "@/app/actions";

Build docs developers (and LLMs) love