From 061ef4b6232583847cd964733cc4442add80b6bf Mon Sep 17 00:00:00 2001 From: Ronni Skansing Date: Thu, 23 Oct 2025 12:19:53 +0200 Subject: [PATCH] add company stats Signed-off-by: Ronni Skansing --- backend/app/administration.go | 8 + backend/controller/campaign.go | 103 ++++- backend/database/campaignStats.go | 2 +- backend/repository/campaign.go | 82 ++-- backend/service/campaign.go | 200 ++++++++- frontend/src/lib/api/api.js | 80 +++- frontend/src/routes/company/+page.svelte | 5 + .../routes/company/[id]/stats/+page.svelte | 399 ++++++++++++++++++ frontend/src/routes/dashboard/+page.svelte | 32 +- frontend/src/routes/proxy/+page.svelte | 3 - 10 files changed, 843 insertions(+), 71 deletions(-) create mode 100644 frontend/src/routes/company/[id]/stats/+page.svelte diff --git a/backend/app/administration.go b/backend/app/administration.go index 4bc62c2..ae05c69 100644 --- a/backend/app/administration.go +++ b/backend/app/administration.go @@ -144,6 +144,10 @@ const ( ROUTE_V1_CAMPAIGN_STATS = "/api/v1/campaign/statistics" ROUTE_V1_CAMPAIGN_STATS_ID = "/api/v1/campaign/:id/stats" ROUTE_V1_CAMPAIGN_STATS_ALL = "/api/v1/campaign/stats/all" + ROUTE_V1_CAMPAIGN_STATS_CREATE = "/api/v1/campaign/stats" + ROUTE_V1_CAMPAIGN_STATS_MANUAL = "/api/v1/campaign/stats/manual" + ROUTE_V1_CAMPAIGN_STATS_UPDATE = "/api/v1/campaign/stats/:id" + ROUTE_V1_CAMPAIGN_STATS_DELETE = "/api/v1/campaign/stats/:id" ROUTE_V1_CAMPAIGN_UPLOAD_REPORTED = "/api/v1/campaign/:id/upload/reported" // campaign-recipient ROUTE_V1_CAMPAIGN_RECIPIENT_EMAIL = "/api/v1/campaign/recipient/:id/email" @@ -381,6 +385,10 @@ func setupRoutes( GET(ROUTE_V1_CAMPAIGN_RESULT_STATS, middleware.SessionHandler, controllers.Campaign.GetResultStats). GET(ROUTE_V1_CAMPAIGN_STATS_ID, middleware.SessionHandler, controllers.Campaign.GetCampaignStats). GET(ROUTE_V1_CAMPAIGN_STATS_ALL, middleware.SessionHandler, controllers.Campaign.GetAllCampaignStats). + POST(ROUTE_V1_CAMPAIGN_STATS_CREATE, middleware.SessionHandler, controllers.Campaign.CreateCampaignStats). + GET(ROUTE_V1_CAMPAIGN_STATS_MANUAL, middleware.SessionHandler, controllers.Campaign.GetManualCampaignStats). + PUT(ROUTE_V1_CAMPAIGN_STATS_UPDATE, middleware.SessionHandler, controllers.Campaign.UpdateCampaignStats). + DELETE(ROUTE_V1_CAMPAIGN_STATS_DELETE, middleware.SessionHandler, controllers.Campaign.DeleteCampaignStatsManual). GET(ROUTE_V1_CAMPAIGN_ID, middleware.SessionHandler, controllers.Campaign.GetByID). GET(ROUTE_V1_CAMPAIGN_NAME, middleware.SessionHandler, controllers.Campaign.GetByName). POST(ROUTE_V1_CAMPAIGN, middleware.SessionHandler, controllers.Campaign.Create). diff --git a/backend/controller/campaign.go b/backend/controller/campaign.go index b6f8b79..dcdd4b5 100644 --- a/backend/controller/campaign.go +++ b/backend/controller/campaign.go @@ -946,21 +946,112 @@ func (c *Campaign) GetAllCampaignStats(g *gin.Context) { return } // parse request - queryArgs, ok := c.handleQueryArgs(g) - if !ok { - return - } - queryArgs.RemapOrderBy(allowedCampaignColumns) companyID := companyIDFromRequestQuery(g) // get stats - stats, err := c.CampaignService.GetAllCampaignStats(g.Request.Context(), session, queryArgs, companyID) + stats, err := c.CampaignService.GetAllCampaignStats(g.Request.Context(), session, companyID) if ok := c.handleErrors(g, err); !ok { return } c.Response.OK(g, stats) } +// CreateCampaignStats creates manual campaign statistics +func (c *Campaign) CreateCampaignStats(g *gin.Context) { + // handle session + session, _, ok := c.handleSession(g) + if !ok { + return + } + + // parse request body + var req database.CampaignStats + + if err := g.ShouldBindJSON(&req); err != nil { + c.Response.BadRequest(g) + return + } + + // create stats + stats, err := c.CampaignService.CreateManualCampaignStats(g.Request.Context(), session, &req) + if ok := c.handleErrors(g, err); !ok { + return + } + + c.Response.OK(g, stats) +} + +// GetManualCampaignStats gets all manual campaign statistics (those without campaignID) +func (c *Campaign) GetManualCampaignStats(g *gin.Context) { + // handle session + session, _, ok := c.handleSession(g) + if !ok { + return + } + // parse request + companyID := companyIDFromRequestQuery(g) + + // get manual stats (those with null campaignID) + stats, err := c.CampaignService.GetManualCampaignStats(g.Request.Context(), session, companyID) + if ok := c.handleErrors(g, err); !ok { + return + } + c.Response.OK(g, stats) +} + +// UpdateCampaignStats updates manual campaign statistics by ID +func (c *Campaign) UpdateCampaignStats(g *gin.Context) { + // handle session + session, _, ok := c.handleSession(g) + if !ok { + return + } + // parse request + id, ok := c.handleParseIDParam(g) + if !ok { + return + } + + // parse request body + var req database.CampaignStats + + if err := g.ShouldBindJSON(&req); err != nil { + c.Response.BadRequest(g) + return + } + + // update stats + req.ID = id + stats, err := c.CampaignService.UpdateManualCampaignStats(g.Request.Context(), session, &req) + if ok := c.handleErrors(g, err); !ok { + return + } + + c.Response.OK(g, stats) +} + +// DeleteCampaignStatsManual deletes manual campaign statistics by ID +func (c *Campaign) DeleteCampaignStatsManual(g *gin.Context) { + // handle session + session, _, ok := c.handleSession(g) + if !ok { + return + } + // parse request + id, ok := c.handleParseIDParam(g) + if !ok { + return + } + + // delete manual stats + err := c.CampaignService.DeleteManualCampaignStats(g.Request.Context(), session, id) + if ok := c.handleErrors(g, err); !ok { + return + } + + c.Response.OK(g, gin.H{"message": "Campaign stats deleted successfully"}) +} + // UploadReportedCSV uploads a CSV file with reported recipients func (c *Campaign) UploadReportedCSV(g *gin.Context) { // handle session diff --git a/backend/database/campaignStats.go b/backend/database/campaignStats.go index 2bc4747..818ee29 100644 --- a/backend/database/campaignStats.go +++ b/backend/database/campaignStats.go @@ -17,7 +17,7 @@ type CampaignStats struct { UpdatedAt *time.Time `gorm:"not null;" json:"updatedAt"` // Campaign reference - CampaignID *uuid.UUID `gorm:"not null;unique;index;type:uuid;" json:"campaignId"` + CampaignID *uuid.UUID `gorm:"index;type:uuid;" json:"campaignId"` CampaignName string `gorm:"not null;" json:"campaignName"` CompanyID *uuid.UUID `gorm:"index;type:uuid;" json:"companyId"` // nullable for global campaigns diff --git a/backend/repository/campaign.go b/backend/repository/campaign.go index 42c0045..ad23f40 100644 --- a/backend/repository/campaign.go +++ b/backend/repository/campaign.go @@ -1666,42 +1666,22 @@ func (r *Campaign) GetCampaignStats(ctx context.Context, campaignID *uuid.UUID) return &stats, nil } -// GetAllCampaignStats retrieves campaign statistics with pagination and filtering -func (r *Campaign) GetAllCampaignStats(ctx context.Context, companyID *uuid.UUID, options *vo.QueryArgs) ([]database.CampaignStats, error) { +// GetAllCampaignStats retrieves all campaign statistics +func (r *Campaign) GetAllCampaignStats(ctx context.Context, companyID *uuid.UUID) ([]database.CampaignStats, error) { var stats []database.CampaignStats - db := r.DB.WithContext(ctx) + db := r.DB.WithContext(ctx).Order("created_at DESC") if companyID != nil { db = db.Where("company_id = ?", companyID) } - if options != nil { - if options.Search != "" { - db = db.Where("campaign_name ILIKE ?", "%"+options.Search+"%") - } - - if options.OrderBy != "" { - sortColumn := options.OrderBy - if options.Desc { - sortColumn += " DESC" - } - db = db.Order(sortColumn) - } else { - db = db.Order("campaign_closed_at DESC") - } - - if options.Limit > 0 { - db = db.Offset(options.Offset).Limit(options.Limit) - } - } - res := db.Find(&stats) return stats, res.Error } // GetCampaignStatsCount returns the total count of campaign statistics -func (r *Campaign) GetCampaignStatsCount(ctx context.Context, companyID *uuid.UUID, search string) (int64, error) { +func (r *Campaign) GetCampaignStatsCount(ctx context.Context, companyID *uuid.UUID) (int64, error) { var count int64 db := r.DB.WithContext(ctx).Model(&database.CampaignStats{}) @@ -1710,10 +1690,6 @@ func (r *Campaign) GetCampaignStatsCount(ctx context.Context, companyID *uuid.UU db = db.Where("company_id = ?", companyID) } - if search != "" { - db = db.Where("campaign_name ILIKE ?", "%"+search+"%") - } - res := db.Count(&count) return count, res.Error } @@ -1723,3 +1699,53 @@ func (r *Campaign) DeleteCampaignStats(ctx context.Context, campaignID *uuid.UUI res := r.DB.WithContext(ctx).Where("campaign_id = ?", campaignID).Delete(&database.CampaignStats{}) return res.Error } + +// GetManualCampaignStats retrieves manual campaign statistics (those with null campaignID) +func (r *Campaign) GetManualCampaignStats(ctx context.Context, companyID *uuid.UUID) ([]database.CampaignStats, error) { + var stats []database.CampaignStats + + db := r.DB.WithContext(ctx).Where("campaign_id IS NULL").Order("created_at DESC") + + if companyID != nil { + db = db.Where("company_id = ?", companyID) + } + + res := db.Find(&stats) + return stats, res.Error +} + +// GetManualCampaignStatsCount returns the total count of manual campaign statistics +func (r *Campaign) GetManualCampaignStatsCount(ctx context.Context, companyID *uuid.UUID) (int64, error) { + var count int64 + + db := r.DB.WithContext(ctx).Model(&database.CampaignStats{}).Where("campaign_id IS NULL") + + if companyID != nil { + db = db.Where("company_id = ?", companyID) + } + + res := db.Count(&count) + return count, res.Error +} + +// GetCampaignStatsByID retrieves campaign statistics by stats ID +func (r *Campaign) GetCampaignStatsByID(ctx context.Context, statsID *uuid.UUID) (*database.CampaignStats, error) { + var stats database.CampaignStats + res := r.DB.WithContext(ctx).Where("id = ?", statsID).First(&stats) + if res.Error != nil { + return nil, res.Error + } + return &stats, nil +} + +// UpdateCampaignStats updates campaign statistics by ID +func (r *Campaign) UpdateCampaignStats(ctx context.Context, statsID *uuid.UUID, updateData map[string]interface{}) error { + res := r.DB.WithContext(ctx).Model(&database.CampaignStats{}).Where("id = ?", statsID).Updates(updateData) + return res.Error +} + +// DeleteCampaignStatsByID deletes campaign statistics by stats ID +func (r *Campaign) DeleteCampaignStatsByID(ctx context.Context, statsID *uuid.UUID) error { + res := r.DB.WithContext(ctx).Where("id = ?", statsID).Delete(&database.CampaignStats{}) + return res.Error +} diff --git a/backend/service/campaign.go b/backend/service/campaign.go index 54d4c72..c032631 100644 --- a/backend/service/campaign.go +++ b/backend/service/campaign.go @@ -3838,8 +3838,8 @@ func (c *Campaign) GetCampaignStats(ctx context.Context, session *model.Session, return c.CampaignRepository.GetCampaignStats(ctx, campaignID) } -// GetAllCampaignStats retrieves all campaign statistics with pagination -func (c *Campaign) GetAllCampaignStats(ctx context.Context, session *model.Session, options *vo.QueryArgs, companyID *uuid.UUID) (*model.Result[database.CampaignStats], error) { +// GetAllCampaignStats retrieves all campaign statistics +func (c *Campaign) GetAllCampaignStats(ctx context.Context, session *model.Session, companyID *uuid.UUID) (*model.Result[database.CampaignStats], error) { result := model.NewEmptyResult[database.CampaignStats]() // Check permissions @@ -3853,7 +3853,7 @@ func (c *Campaign) GetAllCampaignStats(ctx context.Context, session *model.Sessi } // Get the data - stats, err := c.CampaignRepository.GetAllCampaignStats(ctx, companyID, options) + stats, err := c.CampaignRepository.GetAllCampaignStats(ctx, companyID) if err != nil { return result, errs.Wrap(err) } @@ -3865,11 +3865,203 @@ func (c *Campaign) GetAllCampaignStats(ctx context.Context, session *model.Sessi } result.Rows = rows - result.HasNextPage = false // Simple implementation without pagination for now + result.HasNextPage = false return result, nil } +// CreateManualCampaignStats creates campaign statistics manually without requiring a campaign +func (c *Campaign) CreateManualCampaignStats(ctx context.Context, session *model.Session, req *database.CampaignStats) (*database.CampaignStats, error) { + // Check permissions + isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL) + if err != nil && !errors.Is(err, errs.ErrAuthorizationFailed) { + c.LogAuthError(err) + return nil, errs.Wrap(err) + } + if !isAuthorized { + return nil, errs.ErrAuthorizationFailed + } + + // Set up the stats record + id := uuid.New() + now := time.Now() + + // Use provided date for created_at and updated_at, or current time if not provided + var statsDate time.Time + if req.CampaignStartDate != nil { + statsDate = *req.CampaignStartDate + } else { + statsDate = now + } + + // Set required fields + req.ID = &id + req.CreatedAt = &statsDate + req.UpdatedAt = &statsDate + req.CampaignID = nil // No campaign reference for manual stats + + // Calculate rates + if req.TotalRecipients > 0 { + req.OpenRate = float64(req.TrackingPixelLoaded) / float64(req.TotalRecipients) * 100 + req.ClickRate = float64(req.WebsiteVisits) / float64(req.TotalRecipients) * 100 + req.SubmissionRate = float64(req.DataSubmissions) / float64(req.TotalRecipients) * 100 + req.ReportRate = float64(req.Reported) / float64(req.TotalRecipients) * 100 + } + + // Calculate total events + req.TotalEvents = req.EmailsSent + req.TrackingPixelLoaded + req.WebsiteVisits + req.DataSubmissions + req.Reported + + // Insert the stats + c.Logger.Debugf("inserting manual campaign stats", "statsID", req.ID.String()) + err = c.CampaignRepository.InsertCampaignStats(ctx, req) + if err != nil { + c.Logger.Errorw("failed to insert manual campaign stats", "error", err, "statsID", req.ID.String()) + return nil, errs.Wrap(err) + } + + c.Logger.Debugf("successfully inserted manual campaign stats", "statsID", req.ID.String()) + return req, nil +} + +// GetManualCampaignStats retrieves manual campaign statistics (those without campaignID) +func (c *Campaign) GetManualCampaignStats(ctx context.Context, session *model.Session, companyID *uuid.UUID) (*model.Result[database.CampaignStats], error) { + result := model.NewEmptyResult[database.CampaignStats]() + + // Check permissions + isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL) + if err != nil && !errors.Is(err, errs.ErrAuthorizationFailed) { + c.LogAuthError(err) + return result, errs.Wrap(err) + } + if !isAuthorized { + return result, errs.ErrAuthorizationFailed + } + + // Get manual stats (those with null campaignID) + stats, err := c.CampaignRepository.GetManualCampaignStats(ctx, companyID) + if err != nil { + return result, errs.Wrap(err) + } + + // Convert to result format with pointers + rows := make([]*database.CampaignStats, len(stats)) + for i := range stats { + rows[i] = &stats[i] + } + + result.Rows = rows + result.HasNextPage = false + + return result, nil +} + +// UpdateManualCampaignStats updates manual campaign statistics +func (c *Campaign) UpdateManualCampaignStats(ctx context.Context, session *model.Session, req *database.CampaignStats) (*database.CampaignStats, error) { + // Check permissions + isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL) + if err != nil && !errors.Is(err, errs.ErrAuthorizationFailed) { + c.LogAuthError(err) + return nil, errs.Wrap(err) + } + if !isAuthorized { + return nil, errs.ErrAuthorizationFailed + } + + // Get existing stats to ensure it's manual (no campaignID) + existingStats, err := c.CampaignRepository.GetCampaignStatsByID(ctx, req.ID) + if err != nil { + return nil, errs.Wrap(err) + } + + // Ensure this is a manual stat (no campaignID) + if existingStats.CampaignID != nil { + return nil, errs.Wrap(errors.New("cannot update system-generated campaign stats")) + } + + // Set updated timestamp + now := time.Now() + req.UpdatedAt = &now + req.CampaignID = nil // Ensure it remains manual + + // Calculate rates + if req.TotalRecipients > 0 { + req.OpenRate = float64(req.TrackingPixelLoaded) / float64(req.TotalRecipients) * 100 + req.ClickRate = float64(req.WebsiteVisits) / float64(req.TotalRecipients) * 100 + req.SubmissionRate = float64(req.DataSubmissions) / float64(req.TotalRecipients) * 100 + req.ReportRate = float64(req.Reported) / float64(req.TotalRecipients) * 100 + } + + // Calculate total events + req.TotalEvents = req.EmailsSent + req.TrackingPixelLoaded + req.WebsiteVisits + req.DataSubmissions + req.Reported + + // Prepare update data + updateData := map[string]interface{}{ + "updated_at": req.UpdatedAt, + "campaign_name": req.CampaignName, + "company_id": req.CompanyID, + "campaign_start_date": req.CampaignStartDate, + "campaign_end_date": req.CampaignEndDate, + "campaign_closed_at": req.CampaignClosedAt, + "total_recipients": req.TotalRecipients, + "total_events": req.TotalEvents, + "emails_sent": req.EmailsSent, + "tracking_pixel_loaded": req.TrackingPixelLoaded, + "website_visits": req.WebsiteVisits, + "data_submissions": req.DataSubmissions, + "reported": req.Reported, + "open_rate": req.OpenRate, + "click_rate": req.ClickRate, + "submission_rate": req.SubmissionRate, + "report_rate": req.ReportRate, + "template_name": req.TemplateName, + "campaign_type": req.CampaignType, + } + + // Update the stats + err = c.CampaignRepository.UpdateCampaignStats(ctx, req.ID, updateData) + if err != nil { + c.Logger.Errorw("failed to update manual campaign stats", "error", err, "statsID", req.ID.String()) + return nil, errs.Wrap(err) + } + + c.Logger.Debugf("successfully updated manual campaign stats", "statsID", req.ID.String()) + return req, nil +} + +// DeleteManualCampaignStats deletes manual campaign statistics by ID +func (c *Campaign) DeleteManualCampaignStats(ctx context.Context, session *model.Session, statsID *uuid.UUID) error { + // Check permissions + isAuthorized, err := IsAuthorized(session, data.PERMISSION_ALLOW_GLOBAL) + if err != nil && !errors.Is(err, errs.ErrAuthorizationFailed) { + c.LogAuthError(err) + return errs.Wrap(err) + } + if !isAuthorized { + return errs.ErrAuthorizationFailed + } + + // Get existing stats to ensure it's manual (no campaignID) + existingStats, err := c.CampaignRepository.GetCampaignStatsByID(ctx, statsID) + if err != nil { + return errs.Wrap(err) + } + + // Ensure this is a manual stat (no campaignID) + if existingStats.CampaignID != nil { + return errs.Wrap(errors.New("cannot delete system-generated campaign stats")) + } + + // Delete the stats + err = c.CampaignRepository.DeleteCampaignStatsByID(ctx, statsID) + if err != nil { + c.Logger.Errorw("failed to delete manual campaign stats", "error", err, "statsID", statsID.String()) + return errs.Wrap(err) + } + + c.Logger.Debugf("successfully deleted manual campaign stats", "statsID", statsID.String()) + return nil +} + // ProcessReportedCSV processes a CSV file with reported recipients func (c *Campaign) ProcessReportedCSV( ctx context.Context, diff --git a/frontend/src/lib/api/api.js b/frontend/src/lib/api/api.js index e05339e..bf944b4 100644 --- a/frontend/src/lib/api/api.js +++ b/frontend/src/lib/api/api.js @@ -862,18 +862,82 @@ export class API { }, /** - * Get all campaign statistics with pagination. + * Get all campaign statistics. * - * @param {TableURLParams} options * @param {string|null} companyID * @returns {Promise} */ - getAllCampaignStats: async (options, companyID = null) => { - return await getJSON( - this.getPath( - `/campaign/stats/all?${appendQuery(options)}${this.appendCompanyQuery(companyID)}` - ) - ); + getAllCampaignStats: async (companyID = null) => { + const companyQuery = companyID ? `?${this.companyQuery(companyID)}` : ''; + return await getJSON(this.getPath(`/campaign/stats/all${companyQuery}`)); + }, + + /** + * Get all manual campaign statistics (those without campaignID). + * + * @param {string|null} companyID + * @returns {Promise} + */ + getManualCampaignStats: async (companyID = null) => { + const companyQuery = companyID ? `?${this.companyQuery(companyID)}` : ''; + return await getJSON(this.getPath(`/campaign/stats/manual${companyQuery}`)); + }, + + /** + * Create manual campaign statistics. + * + * @param {object} stats + * @param {string} stats.campaignName + * @param {string} [stats.companyId] + * @param {number} stats.totalRecipients + * @param {number} stats.emailsSent + * @param {number} stats.trackingPixelLoaded + * @param {number} stats.websiteVisits + * @param {number} stats.dataSubmissions + * @param {number} stats.reported + * @param {string} [stats.templateName] + * @param {string} [stats.campaignType] + * @param {string} [stats.campaignStartDate] - ISO date string + * @param {string} [stats.campaignEndDate] - ISO date string + * @param {string} [stats.campaignClosedAt] - ISO date string + * @returns {Promise} + */ + createStats: async (stats) => { + return await postJSON(this.getPath('/campaign/stats'), stats); + }, + + /** + * Update manual campaign statistics by ID. + * + * @param {string} statsID + * @param {object} stats + * @param {string} stats.campaignName + * @param {string} [stats.companyId] + * @param {number} stats.totalRecipients + * @param {number} stats.emailsSent + * @param {number} stats.trackingPixelLoaded + * @param {number} stats.websiteVisits + * @param {number} stats.dataSubmissions + * @param {number} stats.reported + * @param {string} [stats.templateName] + * @param {string} [stats.campaignType] + * @param {string} [stats.campaignStartDate] - ISO date string + * @param {string} [stats.campaignEndDate] - ISO date string + * @param {string} [stats.campaignClosedAt] - ISO date string + * @returns {Promise} + */ + updateStats: async (statsID, stats) => { + return await putJSON(this.getPath(`/campaign/stats/${statsID}`), stats); + }, + + /** + * Delete manual campaign statistics by ID. + * + * @param {string} statsID + * @returns {Promise} + */ + deleteStats: async (statsID) => { + return await deleteJSON(this.getPath(`/campaign/stats/${statsID}`)); } }; diff --git a/frontend/src/routes/company/+page.svelte b/frontend/src/routes/company/+page.svelte index 3ce6e22..5c7b6dd 100644 --- a/frontend/src/routes/company/+page.svelte +++ b/frontend/src/routes/company/+page.svelte @@ -1,6 +1,7 @@ + + +
+ + Custom Stats - {company.name} + + + Add + + + {#each customStats as stats} + + + + + + + + + + + + + + openUpdateModal(stats.id)} /> + openDeleteAlert(stats)} /> + + + + {/each} +
+ + + + + + Campaign Name + Total Recipients + + Emails Sent + Email Opens (Read) + + + Links Clicked + Data Submissions + + Reported as Phishing + Date + + + + + + + + + onClickDelete(deleteValues.id)} + bind:isVisible={isDeleteAlertVisible} + /> +
diff --git a/frontend/src/routes/dashboard/+page.svelte b/frontend/src/routes/dashboard/+page.svelte index 5f5e40b..54104c2 100644 --- a/frontend/src/routes/dashboard/+page.svelte +++ b/frontend/src/routes/dashboard/+page.svelte @@ -261,15 +261,7 @@ sortOrder: 'desc', perPage: 10 }); - const options = { - page: statsParams.currentPage, - perPage: statsParams.perPage, - sortBy: statsParams.sortBy, - sortOrder: statsParams.sortOrder, - search: statsParams.search, - includeTest: includeTestCampaigns - }; - const res = await api.campaign.getAllCampaignStats(options, contextCompanyID); + const res = await api.campaign.getAllCampaignStats(contextCompanyID); if (!res.success) { throw res.error; } @@ -372,15 +364,7 @@ completedCampaigns = completedRes.data.rows; } - const statsOptions = { - page: 1, - perPage: 10, - sortBy: 'campaign_closed_at', - sortOrder: 'desc', - search: '', - includeTest: includeTestCampaigns - }; - const statsRes = await api.campaign.getAllCampaignStats(statsOptions, contextCompanyID); + const statsRes = await api.campaign.getAllCampaignStats(contextCompanyID); if (statsRes.success) { campaignStats = []; await tick(); @@ -650,9 +634,15 @@ {#each campaignStats as stat} - - {stat.campaignName} - + {#if stat.campaignId} + + {stat.campaignName} + + {:else} + + {stat.campaignName} + + {/if} diff --git a/frontend/src/routes/proxy/+page.svelte b/frontend/src/routes/proxy/+page.svelte index 533d388..119241a 100644 --- a/frontend/src/routes/proxy/+page.svelte +++ b/frontend/src/routes/proxy/+page.svelte @@ -8,7 +8,6 @@ import TextField from '$lib/components/TextField.svelte'; import TableRow from '$lib/components/table/TableRow.svelte'; import TableCell from '$lib/components/table/TableCell.svelte'; - import TableCellLink from '$lib/components/table/TableCellLink.svelte'; import TableUpdateButton from '$lib/components/table/TableUpdateButton.svelte'; import TableDeleteButton from '$lib/components/table/TableDeleteButton2.svelte'; import FormError from '$lib/components/FormError.svelte'; @@ -19,8 +18,6 @@ import Modal from '$lib/components/Modal.svelte'; import FormGrid from '$lib/components/FormGrid.svelte'; import BigButton from '$lib/components/BigButton.svelte'; - import FormColumns from '$lib/components/FormColumns.svelte'; - import FormColumn from '$lib/components/FormColumn.svelte'; import FormFooter from '$lib/components/FormFooter.svelte'; import Table from '$lib/components/table/Table.svelte'; import HeadTitle from '$lib/components/HeadTitle.svelte';