feat: integrate Grafana dashboards into BOC DashboardPage
- Add Infrastructure Health section with CPU/Memory/Disk panels - Add Service Status section with PM2/Docker panels - Create GrafanaPanel component for iframe embedding - Build passes successfully
This commit is contained in:
@@ -0,0 +1,675 @@
|
||||
package employee
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// Handler hanterar Employee Lifecycle API
|
||||
type Handler struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
func NewHandler(db *sql.DB) *Handler {
|
||||
return &Handler{DB: db}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// EMPLOYEE CRUD
|
||||
// ==========================================
|
||||
|
||||
// Employee representerar en medarbetare
|
||||
type Employee struct {
|
||||
ID string `json:"id"`
|
||||
TenantID string `json:"tenant_id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Email string `json:"email"`
|
||||
Phone *string `json:"phone,omitempty"`
|
||||
PersonalNumber *string `json:"personal_number,omitempty"`
|
||||
BirthDate *time.Time `json:"birth_date,omitempty"`
|
||||
AvatarURL *string `json:"avatar_url,omitempty"`
|
||||
Bio *string `json:"bio,omitempty"`
|
||||
EmploymentType string `json:"employment_type"`
|
||||
EmploymentStatus string `json:"employment_status"`
|
||||
StartDate *time.Time `json:"start_date,omitempty"`
|
||||
EndDate *time.Time `json:"end_date,omitempty"`
|
||||
ProbationEndDate *time.Time `json:"probation_end_date,omitempty"`
|
||||
NoticePeriodDays int `json:"notice_period_days"`
|
||||
DepartmentID *string `json:"department_id,omitempty"`
|
||||
TeamID *string `json:"team_id,omitempty"`
|
||||
ManagerID *string `json:"manager_id,omitempty"`
|
||||
RoleID *string `json:"role_id,omitempty"`
|
||||
Salary *float64 `json:"salary,omitempty"`
|
||||
SalaryCurrency string `json:"salary_currency"`
|
||||
BankAccount *string `json:"bank_account,omitempty"`
|
||||
BankClearing *string `json:"bank_clearing,omitempty"`
|
||||
Address *string `json:"address,omitempty"`
|
||||
PostalCode *string `json:"postal_code,omitempty"`
|
||||
City *string `json:"city,omitempty"`
|
||||
Country string `json:"country"`
|
||||
EmergencyContactName *string `json:"emergency_contact_name,omitempty"`
|
||||
EmergencyContactPhone *string `json:"emergency_contact_phone,omitempty"`
|
||||
EmergencyContactRelation *string `json:"emergency_contact_relation,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ListEmployees returnerar alla medarbetare
|
||||
func (h *Handler) ListEmployees(w http.ResponseWriter, r *http.Request) {
|
||||
rows, err := h.DB.Query(`
|
||||
SELECT id, tenant_id, first_name, last_name, email, phone, employment_type, employment_status,
|
||||
start_date, department_id, team_id, manager_id, role_id, created_at
|
||||
FROM employees
|
||||
WHERE employment_status != 'terminated'
|
||||
ORDER BY created_at DESC
|
||||
`)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"database error"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var employees []Employee
|
||||
for rows.Next() {
|
||||
var e Employee
|
||||
var phone sql.NullString
|
||||
var startDate sql.NullTime
|
||||
var deptID, teamID, managerID, roleID sql.NullString
|
||||
|
||||
rows.Scan(&e.ID, &e.TenantID, &e.FirstName, &e.LastName, &e.Email, &phone,
|
||||
&e.EmploymentType, &e.EmploymentStatus, &startDate,
|
||||
&deptID, &teamID, &managerID, &roleID, &e.CreatedAt)
|
||||
|
||||
if phone.Valid { e.Phone = &phone.String }
|
||||
if startDate.Valid { e.StartDate = &startDate.Time }
|
||||
if deptID.Valid { e.DepartmentID = &deptID.String }
|
||||
if teamID.Valid { e.TeamID = &teamID.String }
|
||||
if managerID.Valid { e.ManagerID = &managerID.String }
|
||||
if roleID.Valid { e.RoleID = &roleID.String }
|
||||
|
||||
employees = append(employees, e)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"employees": employees,
|
||||
"total": len(employees),
|
||||
})
|
||||
}
|
||||
|
||||
// GetEmployee returnerar en specifik medarbetare
|
||||
func (h *Handler) GetEmployee(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
|
||||
var e Employee
|
||||
var phone, personalNumber, avatarURL, bio, bankAccount, bankClearing sql.NullString
|
||||
var birthDate, startDate, endDate, probationEnd sql.NullTime
|
||||
var deptID, teamID, managerID, roleID sql.NullString
|
||||
var salary sql.NullFloat64
|
||||
var address, postalCode, city, emergencyName, emergencyPhone, emergencyRelation sql.NullString
|
||||
|
||||
err := h.DB.QueryRow(`
|
||||
SELECT id, tenant_id, first_name, last_name, email, phone, personal_number, birth_date,
|
||||
avatar_url, bio, employment_type, employment_status, start_date, end_date, probation_end_date,
|
||||
notice_period_days, department_id, team_id, manager_id, role_id,
|
||||
salary, salary_currency, bank_account, bank_clearing,
|
||||
address, postal_code, city, country,
|
||||
emergency_contact_name, emergency_contact_phone, emergency_contact_relation,
|
||||
created_at, updated_at
|
||||
FROM employees WHERE id = $1
|
||||
`, id).Scan(
|
||||
&e.ID, &e.TenantID, &e.FirstName, &e.LastName, &e.Email, &phone, &personalNumber, &birthDate,
|
||||
&avatarURL, &bio, &e.EmploymentType, &e.EmploymentStatus, &startDate, &endDate, &probationEnd,
|
||||
&e.NoticePeriodDays, &deptID, &teamID, &managerID, &roleID,
|
||||
&salary, &e.SalaryCurrency, &bankAccount, &bankClearing,
|
||||
&address, &postalCode, &city, &e.Country,
|
||||
&emergencyName, &emergencyPhone, &emergencyRelation,
|
||||
&e.CreatedAt, &e.UpdatedAt,
|
||||
)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
http.Error(w, `{"error":"employee not found"}`, http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"database error"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Sätt nullable fält
|
||||
if phone.Valid { e.Phone = &phone.String }
|
||||
if personalNumber.Valid { e.PersonalNumber = &personalNumber.String }
|
||||
if birthDate.Valid { e.BirthDate = &birthDate.Time }
|
||||
if avatarURL.Valid { e.AvatarURL = &avatarURL.String }
|
||||
if bio.Valid { e.Bio = &bio.String }
|
||||
if startDate.Valid { e.StartDate = &startDate.Time }
|
||||
if endDate.Valid { e.EndDate = &endDate.Time }
|
||||
if probationEnd.Valid { e.ProbationEndDate = &probationEnd.Time }
|
||||
if deptID.Valid { e.DepartmentID = &deptID.String }
|
||||
if teamID.Valid { e.TeamID = &teamID.String }
|
||||
if managerID.Valid { e.ManagerID = &managerID.String }
|
||||
if roleID.Valid { e.RoleID = &roleID.String }
|
||||
if salary.Valid { e.Salary = &salary.Float64 }
|
||||
if bankAccount.Valid { e.BankAccount = &bankAccount.String }
|
||||
if bankClearing.Valid { e.BankClearing = &bankClearing.String }
|
||||
if address.Valid { e.Address = &address.String }
|
||||
if postalCode.Valid { e.PostalCode = &postalCode.String }
|
||||
if city.Valid { e.City = &city.String }
|
||||
if emergencyName.Valid { e.EmergencyContactName = &emergencyName.String }
|
||||
if emergencyPhone.Valid { e.EmergencyContactPhone = &emergencyPhone.String }
|
||||
if emergencyRelation.Valid { e.EmergencyContactRelation = &emergencyRelation.String }
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(e)
|
||||
}
|
||||
|
||||
// CreateEmployee skapar en ny medarbetare
|
||||
func (h *Handler) CreateEmployee(w http.ResponseWriter, r *http.Request) {
|
||||
var req Employee
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var id string
|
||||
err := h.DB.QueryRow(`
|
||||
INSERT INTO employees (tenant_id, first_name, last_name, email, phone, personal_number,
|
||||
employment_type, employment_status, start_date, department_id, team_id, manager_id, role_id,
|
||||
salary, salary_currency, bank_account, bank_clearing,
|
||||
address, postal_code, city, country)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)
|
||||
RETURNING id
|
||||
`, req.TenantID, req.FirstName, req.LastName, req.Email, req.Phone, req.PersonalNumber,
|
||||
req.EmploymentType, req.EmploymentStatus, req.StartDate, req.DepartmentID, req.TeamID, req.ManagerID, req.RoleID,
|
||||
req.Salary, req.SalaryCurrency, req.BankAccount, req.BankClearing,
|
||||
req.Address, req.PostalCode, req.City, req.Country).Scan(&id)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"failed to create employee"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Skapa timeline event
|
||||
h.DB.Exec(`
|
||||
INSERT INTO employee_timeline_events (employee_id, event_type, event_date, title, description)
|
||||
VALUES ($1, 'hired', $2, 'Anställd', 'Anställning påbörjad')
|
||||
`, id, time.Now())
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"id": id,
|
||||
"message": "Employee created",
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateEmployee uppdaterar en medarbetare
|
||||
func (h *Handler) UpdateEmployee(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
|
||||
var req Employee
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
_, err := h.DB.Exec(`
|
||||
UPDATE employees
|
||||
SET first_name = COALESCE($1, first_name),
|
||||
last_name = COALESCE($2, last_name),
|
||||
email = COALESCE($3, email),
|
||||
phone = COALESCE($4, phone),
|
||||
employment_type = COALESCE($5, employment_type),
|
||||
employment_status = COALESCE($6, employment_status),
|
||||
department_id = COALESCE($7, department_id),
|
||||
team_id = COALESCE($8, team_id),
|
||||
manager_id = COALESCE($9, manager_id),
|
||||
role_id = COALESCE($10, role_id),
|
||||
salary = COALESCE($11, salary),
|
||||
updated_at = NOW()
|
||||
WHERE id = $12
|
||||
`, req.FirstName, req.LastName, req.Email, req.Phone,
|
||||
req.EmploymentType, req.EmploymentStatus,
|
||||
req.DepartmentID, req.TeamID, req.ManagerID, req.RoleID,
|
||||
req.Salary, id)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"failed to update employee"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"message": "Employee updated",
|
||||
})
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// TIMELINE
|
||||
// ==========================================
|
||||
|
||||
// TimelineEvent representerar en händelse i tidslinjen
|
||||
type TimelineEvent struct {
|
||||
ID string `json:"id"`
|
||||
EmployeeID string `json:"employee_id"`
|
||||
EventType string `json:"event_type"`
|
||||
EventDate time.Time `json:"event_date"`
|
||||
Title string `json:"title"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// GetTimeline returnerar tidslinje för en medarbetare
|
||||
func (h *Handler) GetTimeline(w http.ResponseWriter, r *http.Request) {
|
||||
employeeID := chi.URLParam(r, "id")
|
||||
|
||||
rows, err := h.DB.Query(`
|
||||
SELECT id, event_type, event_date, title, description, metadata, created_at
|
||||
FROM employee_timeline_events
|
||||
WHERE employee_id = $1
|
||||
ORDER BY event_date DESC, created_at DESC
|
||||
`, employeeID)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"database error"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var events []TimelineEvent
|
||||
for rows.Next() {
|
||||
var e TimelineEvent
|
||||
var desc sql.NullString
|
||||
var meta []byte
|
||||
rows.Scan(&e.ID, &e.EventType, &e.EventDate, &e.Title, &desc, &meta, &e.CreatedAt)
|
||||
if desc.Valid { e.Description = &desc.String }
|
||||
if len(meta) > 0 {
|
||||
json.Unmarshal(meta, &e.Metadata)
|
||||
}
|
||||
e.EmployeeID = employeeID
|
||||
events = append(events, e)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"events": events,
|
||||
"total": len(events),
|
||||
})
|
||||
}
|
||||
|
||||
// AddTimelineEvent lägger till en händelse
|
||||
func (h *Handler) AddTimelineEvent(w http.ResponseWriter, r *http.Request) {
|
||||
employeeID := chi.URLParam(r, "id")
|
||||
|
||||
var req struct {
|
||||
EventType string `json:"event_type"`
|
||||
EventDate time.Time `json:"event_date"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
metaJSON, _ := json.Marshal(req.Metadata)
|
||||
|
||||
var id string
|
||||
err := h.DB.QueryRow(`
|
||||
INSERT INTO employee_timeline_events (employee_id, event_type, event_date, title, description, metadata)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id
|
||||
`, employeeID, req.EventType, req.EventDate, req.Title, req.Description, metaJSON).Scan(&id)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"failed to add event"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"id": id,
|
||||
"message": "Timeline event added",
|
||||
})
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// COMPETENCES
|
||||
// ==========================================
|
||||
|
||||
// Competence representerar en kompetens
|
||||
type Competence struct {
|
||||
ID string `json:"id"`
|
||||
TenantID string `json:"tenant_id"`
|
||||
Name string `json:"name"`
|
||||
Category string `json:"category"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// EmployeeCompetence representerar en medarbetares kompetens
|
||||
type EmployeeCompetence struct {
|
||||
ID string `json:"id"`
|
||||
EmployeeID string `json:"employee_id"`
|
||||
CompetenceID string `json:"competence_id"`
|
||||
Competence Competence `json:"competence"`
|
||||
Level int `json:"level"`
|
||||
AcquiredDate *time.Time `json:"acquired_date,omitempty"`
|
||||
ValidatedBy *string `json:"validated_by,omitempty"`
|
||||
ValidationDate *time.Time `json:"validation_date,omitempty"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
|
||||
// GetCompetences returnerar kompetenser för en medarbetare
|
||||
func (h *Handler) GetCompetences(w http.ResponseWriter, r *http.Request) {
|
||||
employeeID := chi.URLParam(r, "id")
|
||||
|
||||
rows, err := h.DB.Query(`
|
||||
SELECT ec.id, ec.competence_id, c.name, c.category, c.description,
|
||||
ec.level, ec.acquired_date, ec.validated_by, ec.validation_date, ec.notes
|
||||
FROM employee_competences ec
|
||||
JOIN competences c ON ec.competence_id = c.id
|
||||
WHERE ec.employee_id = $1
|
||||
ORDER BY c.category, c.name
|
||||
`, employeeID)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"database error"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var competences []EmployeeCompetence
|
||||
for rows.Next() {
|
||||
var ec EmployeeCompetence
|
||||
var acquired, validation sql.NullTime
|
||||
var validatedBy sql.NullString
|
||||
|
||||
rows.Scan(&ec.ID, &ec.CompetenceID, &ec.Competence.Name, &ec.Competence.Category, &ec.Competence.Description,
|
||||
&ec.Level, &acquired, &validatedBy, &validation, &ec.Notes)
|
||||
|
||||
if acquired.Valid { ec.AcquiredDate = &acquired.Time }
|
||||
if validatedBy.Valid { ec.ValidatedBy = &validatedBy.String }
|
||||
if validation.Valid { ec.ValidationDate = &validation.Time }
|
||||
|
||||
competences = append(competences, ec)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"competences": competences,
|
||||
"total": len(competences),
|
||||
})
|
||||
}
|
||||
|
||||
// AddCompetence lägger till en kompetens
|
||||
func (h *Handler) AddCompetence(w http.ResponseWriter, r *http.Request) {
|
||||
employeeID := chi.URLParam(r, "id")
|
||||
|
||||
var req struct {
|
||||
CompetenceID string `json:"competence_id"`
|
||||
Level int `json:"level"`
|
||||
AcquiredDate *time.Time `json:"acquired_date"`
|
||||
Notes string `json:"notes"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var id string
|
||||
err := h.DB.QueryRow(`
|
||||
INSERT INTO employee_competences (employee_id, competence_id, level, acquired_date, notes)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id
|
||||
`, employeeID, req.CompetenceID, req.Level, req.AcquiredDate, req.Notes).Scan(&id)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"failed to add competence"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Lägg till timeline event
|
||||
h.DB.Exec(`
|
||||
INSERT INTO employee_timeline_events (employee_id, event_type, event_date, title, description)
|
||||
VALUES ($1, 'competence_added', NOW(), 'Kompetens tillagd', $2)
|
||||
`, employeeID, req.Notes)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"id": id,
|
||||
"message": "Competence added",
|
||||
})
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// DOCUMENTS
|
||||
// ==========================================
|
||||
|
||||
// EmployeeDocument representerar ett dokument
|
||||
type EmployeeDocument struct {
|
||||
ID string `json:"id"`
|
||||
EmployeeID string `json:"employee_id"`
|
||||
TemplateID *string `json:"template_id,omitempty"`
|
||||
DocumentType string `json:"document_type"`
|
||||
Title string `json:"title"`
|
||||
SignatureStatus string `json:"signature_status"`
|
||||
SignedByEmployeeAt *time.Time `json:"signed_by_employee_at,omitempty"`
|
||||
SignedByCompanyAt *time.Time `json:"signed_by_company_at,omitempty"`
|
||||
Version int `json:"version"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// GetDocuments returnerar dokument för en medarbetare
|
||||
func (h *Handler) GetDocuments(w http.ResponseWriter, r *http.Request) {
|
||||
employeeID := chi.URLParam(r, "id")
|
||||
|
||||
rows, err := h.DB.Query(`
|
||||
SELECT id, template_id, document_type, title, signature_status,
|
||||
signed_by_employee_at, signed_by_company_at, version, created_at
|
||||
FROM employee_documents
|
||||
WHERE employee_id = $1
|
||||
ORDER BY created_at DESC
|
||||
`, employeeID)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"database error"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var documents []EmployeeDocument
|
||||
for rows.Next() {
|
||||
var d EmployeeDocument
|
||||
var templateID sql.NullString
|
||||
var signedEmp, signedComp sql.NullTime
|
||||
|
||||
rows.Scan(&d.ID, &templateID, &d.DocumentType, &d.Title, &d.SignatureStatus,
|
||||
&signedEmp, &signedComp, &d.Version, &d.CreatedAt)
|
||||
|
||||
if templateID.Valid { d.TemplateID = &templateID.String }
|
||||
if signedEmp.Valid { d.SignedByEmployeeAt = &signedEmp.Time }
|
||||
if signedComp.Valid { d.SignedByCompanyAt = &signedComp.Time }
|
||||
|
||||
documents = append(documents, d)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"documents": documents,
|
||||
"total": len(documents),
|
||||
})
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// TRAININGS
|
||||
// ==========================================
|
||||
|
||||
// EmployeeTraining representerar en medarbetares utbildning
|
||||
type EmployeeTraining struct {
|
||||
ID string `json:"id"`
|
||||
TrainingID string `json:"training_id"`
|
||||
TrainingTitle string `json:"training_title"`
|
||||
Status string `json:"status"`
|
||||
ProgressPercent int `json:"progress_percent"`
|
||||
StartedAt *time.Time `json:"started_at,omitempty"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
||||
Score *int `json:"score,omitempty"`
|
||||
CertificateURL *string `json:"certificate_url,omitempty"`
|
||||
}
|
||||
|
||||
// GetTrainings returnerar utbildningar för en medarbetare
|
||||
func (h *Handler) GetTrainings(w http.ResponseWriter, r *http.Request) {
|
||||
employeeID := chi.URLParam(r, "id")
|
||||
|
||||
rows, err := h.DB.Query(`
|
||||
SELECT et.id, et.training_id, t.title, et.status, et.progress_percent,
|
||||
et.started_at, et.completed_at, et.expires_at, et.score, et.certificate_url
|
||||
FROM employee_trainings et
|
||||
JOIN trainings t ON et.training_id = t.id
|
||||
WHERE et.employee_id = $1
|
||||
ORDER BY et.created_at DESC
|
||||
`, employeeID)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"database error"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var trainings []EmployeeTraining
|
||||
for rows.Next() {
|
||||
var et EmployeeTraining
|
||||
var started, completed, expires sql.NullTime
|
||||
var score sql.NullInt64
|
||||
var certURL sql.NullString
|
||||
|
||||
rows.Scan(&et.ID, &et.TrainingID, &et.TrainingTitle, &et.Status, &et.ProgressPercent,
|
||||
&started, &completed, &expires, &score, &certURL)
|
||||
|
||||
if started.Valid { et.StartedAt = &started.Time }
|
||||
if completed.Valid { et.CompletedAt = &completed.Time }
|
||||
if expires.Valid { et.ExpiresAt = &expires.Time }
|
||||
if score.Valid { s := int(score.Int64); et.Score = &s }
|
||||
if certURL.Valid { et.CertificateURL = &certURL.String }
|
||||
|
||||
trainings = append(trainings, et)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"trainings": trainings,
|
||||
"total": len(trainings),
|
||||
})
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// TASKS
|
||||
// ==========================================
|
||||
|
||||
// EmployeeTask representerar en uppgift
|
||||
type EmployeeTask struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
Priority string `json:"priority"`
|
||||
DueDate *time.Time `json:"due_date,omitempty"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||
Source string `json:"source"`
|
||||
AssignedAt time.Time `json:"assigned_at"`
|
||||
}
|
||||
|
||||
// GetTasks returnerar uppgifter för en medarbetare
|
||||
func (h *Handler) GetTasks(w http.ResponseWriter, r *http.Request) {
|
||||
employeeID := chi.URLParam(r, "id")
|
||||
|
||||
rows, err := h.DB.Query(`
|
||||
SELECT id, title, description, status, priority, due_date, completed_at, source, assigned_at
|
||||
FROM employee_tasks
|
||||
WHERE employee_id = $1
|
||||
ORDER BY
|
||||
CASE status WHEN 'pending' THEN 1 WHEN 'in_progress' THEN 2 ELSE 3 END,
|
||||
CASE priority WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 ELSE 4 END,
|
||||
due_date ASC
|
||||
`, employeeID)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"database error"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var tasks []EmployeeTask
|
||||
for rows.Next() {
|
||||
var t EmployeeTask
|
||||
var dueDate, completed sql.NullTime
|
||||
|
||||
rows.Scan(&t.ID, &t.Title, &t.Description, &t.Status, &t.Priority, &dueDate, &completed, &t.Source, &t.AssignedAt)
|
||||
if dueDate.Valid { t.DueDate = &dueDate.Time }
|
||||
if completed.Valid { t.CompletedAt = &completed.Time }
|
||||
|
||||
tasks = append(tasks, t)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"tasks": tasks,
|
||||
"total": len(tasks),
|
||||
})
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// PERFORMANCE
|
||||
// ==========================================
|
||||
|
||||
// PerformanceReview representerar en prestationsbedömning
|
||||
type PerformanceReview struct {
|
||||
ID string `json:"id"`
|
||||
ReviewerID string `json:"reviewer_id"`
|
||||
ReviewPeriodStart time.Time `json:"review_period_start"`
|
||||
ReviewPeriodEnd time.Time `json:"review_period_end"`
|
||||
ReviewType string `json:"review_type"`
|
||||
OverallRating *int `json:"overall_rating,omitempty"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// GetPerformance returnerar prestationsbedömningar
|
||||
func (h *Handler) GetPerformance(w http.ResponseWriter, r *http.Request) {
|
||||
employeeID := chi.URLParam(r, "id")
|
||||
|
||||
rows, err := h.DB.Query(`
|
||||
SELECT id, reviewer_id, review_period_start, review_period_end, review_type, overall_rating, status, created_at
|
||||
FROM performance_reviews
|
||||
WHERE employee_id = $1
|
||||
ORDER BY review_period_end DESC
|
||||
`, employeeID)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"database error"}`, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var reviews []PerformanceReview
|
||||
for rows.Next() {
|
||||
var pr PerformanceReview
|
||||
var rating sql.NullInt64
|
||||
|
||||
rows.Scan(&pr.ID, &pr.ReviewerID, &pr.ReviewPeriodStart, &pr.ReviewPeriodEnd,
|
||||
&pr.ReviewType, &rating, &pr.Status, &pr.CreatedAt)
|
||||
if rating.Valid { r := int(rating.Int64); pr.OverallRating = &r }
|
||||
|
||||
reviews = append(reviews, pr)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"reviews": reviews,
|
||||
"total": len(reviews),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user