Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions www/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
<script src="js/controllers/event_types.js"></script>
<script src="js/controllers/teams.js"></script>
<script src="js/controllers/annotation_controller.js"></script>
<script src="js/controllers/support_tickets.js"></script>
</head>

<body ng-app="TagApp">
Expand Down
19 changes: 19 additions & 0 deletions www/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ angular.module('TagApp', [
'eventTypes.controller',
'oldFiles.controller',
'teams.controller',
'supportTickets.controller',
'recorder.service',
'cameraMovement.service',
'recorderControll.service',
Expand Down Expand Up @@ -172,6 +173,24 @@ angular.module('TagApp', [
}
}

}).state('app.support_tickets', {
url: '/support_tickets',
views: {
'menuContent': {
templateUrl: 'templates/support_tickets.html',
controller: 'SupportTicketsCtrl'
}
}

}).state('app.view_ticket', {
url: '/view_ticket/{id}',
views: {
'menuContent': {
templateUrl: 'templates/view_ticket.html',
controller: 'ViewTicketCtrl'
}
}

});
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/login');
Expand Down
199 changes: 199 additions & 0 deletions www/js/controllers/support_tickets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
angular.module('supportTickets.controller', [])

.controller('SupportTicketsCtrl', function($scope, $rootScope, $ionicPopup, $state) {
$scope.$on("$ionicView.beforeEnter", function() {
if (!$rootScope.isUser()) {
$state.go('login');
}
$scope.loadTickets();
});

$scope.tickets = [];
$scope.statusFilter = 'all';

$scope.loadTickets = function() {
if (localStorage.support_tickets) {
$scope.tickets = JSON.parse(localStorage.support_tickets);
} else {
$scope.tickets = [];
}
};

$scope.saveTickets = function() {
localStorage.support_tickets = JSON.stringify($scope.tickets);
};

$scope.filteredTickets = function() {
if ($scope.statusFilter === 'all') return $scope.tickets;
return $scope.tickets.filter(function(t) { return t.status === $scope.statusFilter; });
};

$scope.setFilter = function(status) {
$scope.statusFilter = status;
};

$scope.priorityClass = function(priority) {
return { low: 'energized', medium: 'assertive', high: 'assertive' }[priority] || '';
};

$scope.statusLabel = function(status) {
var labels = { open: 'Open', in_progress: 'In Progress', resolved: 'Resolved', closed: 'Closed' };
return labels[status] || status;
};

$scope.openNewTicketPopup = function() {
$scope.newTicket = { priority: 'medium', category: 'technical' };

var template =
'<div class="list">' +
'<label class="item item-input">' +
'<input type="text" ng-model="newTicket.title" placeholder="Title *">' +
'</label>' +
'<label class="item item-input">' +
'<textarea ng-model="newTicket.description" placeholder="Description" rows="3" style="min-height:70px;width:100%;resize:none;"></textarea>' +
'</label>' +
'<div class="item item-input">' +
'<span class="input-label">Priority</span>' +
'<select ng-model="newTicket.priority" style="width:100%">' +
'<option value="low">Low</option>' +
'<option value="medium" selected>Medium</option>' +
'<option value="high">High</option>' +
'</select>' +
'</div>' +
'<div class="item item-input">' +
'<span class="input-label">Category</span>' +
'<select ng-model="newTicket.category" style="width:100%">' +
'<option value="technical">Technical</option>' +
'<option value="billing">Billing</option>' +
'<option value="feature">Feature Request</option>' +
'<option value="other">Other</option>' +
'</select>' +
'</div>' +
'</div>';

$ionicPopup.show({
template: template,
title: 'New Support Ticket',
scope: $scope,
buttons: [
{ text: 'Cancel' },
{
text: '<b>Submit</b>',
type: 'button-positive',
onTap: function(e) {
if (!$scope.newTicket.title) {
e.preventDefault();
} else {
return $scope.newTicket;
}
}
}
]
}).then(function(ticket) {
if (!ticket) return;
var now = new Date().toISOString();
$scope.tickets.unshift({
id: Date.now().toString(),
title: ticket.title,
description: ticket.description || '',
priority: ticket.priority,
category: ticket.category,
status: 'open',
created_at: now,
updated_at: now,
comments: []
});
$scope.saveTickets();
});
};

$scope.deleteTicket = function(index) {
$ionicPopup.confirm({
title: 'Delete Ticket',
template: 'Are you sure you want to delete this ticket?'
}).then(function(confirmed) {
if (confirmed) {
$scope.tickets.splice(index, 1);
$scope.saveTickets();
}
});
};

$scope.viewTicket = function(ticket) {
$state.go('app.view_ticket', { id: ticket.id });
};
})

.controller('ViewTicketCtrl', function($scope, $rootScope, $ionicPopup, $state, $stateParams, $ionicHistory) {
$scope.$on("$ionicView.beforeEnter", function() {
if (!$rootScope.isUser()) {
$state.go('login');
}
$scope.loadTicket();
});

$scope.ticket = null;
$scope.newComment = '';

$scope.loadTicket = function() {
var tickets = localStorage.support_tickets ? JSON.parse(localStorage.support_tickets) : [];
$scope.ticket = tickets.filter(function(t) { return t.id === $stateParams.id; })[0] || null;
$scope.ticketIndex = tickets.indexOf($scope.ticket);
};

$scope.saveTickets = function(tickets) {
localStorage.support_tickets = JSON.stringify(tickets);
};

$scope.updateTicket = function() {
var tickets = localStorage.support_tickets ? JSON.parse(localStorage.support_tickets) : [];
var idx = tickets.findIndex(function(t) { return t.id === $scope.ticket.id; });
if (idx !== -1) {
$scope.ticket.updated_at = new Date().toISOString();
tickets[idx] = $scope.ticket;
$scope.saveTickets(tickets);
}
};

$scope.setStatus = function(status) {
if (!$scope.ticket) return;
$scope.ticket.status = status;
$scope.updateTicket();
};

$scope.addComment = function() {
if (!$scope.newComment || !$scope.newComment.trim()) return;
$scope.ticket.comments.push({
id: Date.now().toString(),
text: $scope.newComment.trim(),
created_at: new Date().toISOString()
});
$scope.newComment = '';
$scope.updateTicket();
};

$scope.deleteComment = function(index) {
$scope.ticket.comments.splice(index, 1);
$scope.updateTicket();
};

$scope.priorityClass = function(priority) {
var map = { low: 'balanced', medium: 'energized', high: 'assertive' };
return map[priority] || 'stable';
};

$scope.statusClass = function(status) {
var map = { open: 'positive', in_progress: 'energized', resolved: 'balanced', closed: 'stable' };
return map[status] || 'stable';
};

$scope.formatDate = function(iso) {
if (!iso) return '';
var d = new Date(iso);
return d.toLocaleDateString() + ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
};

$scope.goBack = function() {
$ionicHistory.goBack();
};
});
3 changes: 3 additions & 0 deletions www/templates/menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ <h1 class="title"></h1>
<ion-item menu-toggle href="#/app/teams">
<i class='ion-person-stalker'></i> Team Setup
</ion-item>
<ion-item menu-toggle href="#/app/support_tickets">
<i class='ion-help-buoy'></i> Support Tickets
</ion-item>
</ion-list>
</ion-content>
</ion-side-menu>
Expand Down
82 changes: 82 additions & 0 deletions www/templates/support_tickets.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<ion-view view-title="Support Tickets">
<ion-content>
<!-- Filter bar -->
<div class="button-bar" style="padding:8px;">
<button class="button button-small"
ng-class="statusFilter === 'all' ? 'button-positive' : 'button-outline button-positive'"
ng-click="setFilter('all')">All</button>
<button class="button button-small"
ng-class="statusFilter === 'open' ? 'button-positive' : 'button-outline button-positive'"
ng-click="setFilter('open')">Open</button>
<button class="button button-small"
ng-class="statusFilter === 'in_progress' ? 'button-energized' : 'button-outline button-energized'"
ng-click="setFilter('in_progress')">In Progress</button>
<button class="button button-small"
ng-class="statusFilter === 'resolved' ? 'button-balanced' : 'button-outline button-balanced'"
ng-click="setFilter('resolved')">Resolved</button>
<button class="button button-small"
ng-class="statusFilter === 'closed' ? 'button-stable' : 'button-outline button-stable'"
ng-click="setFilter('closed')">Closed</button>
</div>

<!-- Empty state -->
<div ng-if="filteredTickets().length === 0" style="text-align:center;padding:40px 20px;color:#999;">
<i class="icon ion-help-buoy" style="font-size:48px;display:block;margin-bottom:12px;"></i>
<p>No tickets found.</p>
<p ng-if="statusFilter !== 'all'" style="font-size:13px;">Try changing the filter above.</p>
</div>

<!-- Ticket list -->
<ion-list>
<ion-item ng-repeat="ticket in filteredTickets() track by ticket.id"
ng-click="viewTicket(ticket)"
class="item-icon-right"
style="padding-right:50px;">

<!-- Priority badge -->
<span class="badge"
ng-class="{
'badge-balanced': ticket.priority === 'low',
'badge-energized': ticket.priority === 'medium',
'badge-assertive': ticket.priority === 'high'
}"
style="float:right;margin-top:2px;text-transform:capitalize;">
{{ticket.priority}}
</span>

<h2 style="font-weight:bold;font-size:14px;margin-right:60px;">{{ticket.title}}</h2>
<p style="font-size:12px;color:#888;margin:3px 0;">
<span class="badge"
ng-class="{
'badge-positive': ticket.status === 'open',
'badge-energized': ticket.status === 'in_progress',
'badge-balanced': ticket.status === 'resolved',
'badge-stable': ticket.status === 'closed'
}"
style="text-transform:capitalize;">
{{statusLabel(ticket.status)}}
</span>
&nbsp;{{ticket.category | uppercase}}
&bull; {{ticket.comments.length}} comment{{ticket.comments.length !== 1 ? 's' : ''}}
</p>
<p style="font-size:11px;color:#bbb;margin:2px 0;">
{{ticket.created_at | date:'mediumDate'}}
</p>

<i class="icon ion-chevron-right icon-accessory"></i>

<ion-option-button class="button-assertive" ng-click="deleteTicket($index)">
<i class="icon ion-trash-a"></i>
</ion-option-button>
</ion-item>
</ion-list>
</ion-content>

<ion-footer-bar class="bar-dark">
<div class="button-bar">
<a class="button button-positive" ng-click="openNewTicketPopup()">
<i class="icon ion-plus"></i> New Ticket
</a>
</div>
</ion-footer-bar>
</ion-view>
Loading