Added a filter onto the movies requests page for some inital feedback

pull/1925/head
Jamie 7 years ago
parent 8621a754f5
commit 71ff8fe301

@ -17,5 +17,6 @@ namespace Ombi.Core.Engine.Interfaces
Task<RequestEngineResult> ApproveMovie(MovieRequests request);
Task<RequestEngineResult> ApproveMovieById(int requestId);
Task<RequestEngineResult> DenyMovieById(int modelId);
IEnumerable<MovieRequests> Filter(FilterViewModel vm);
}
}

@ -335,22 +335,41 @@ namespace Ombi.Core.Engine
return new RequestEngineResult { Result = true, Message = $"{movieName} has been successfully added!" };
}
public async Task<IEnumerable<MovieRequests>> GetApprovedRequests()
public IEnumerable<MovieRequests> Filter(FilterViewModel vm)
{
var allRequests = MovieRepository.GetWithUser();
return await allRequests.Where(x => x.Approved && !x.Available).ToListAsync();
}
var requests = MovieRepository.GetWithUser();
switch (vm.AvailabilityFilter)
{
case FilterType.None:
break;
case FilterType.Available:
requests = requests.Where(x => x.Available);
break;
case FilterType.NotAvailable:
requests = requests.Where(x => !x.Available);
break;
default:
throw new ArgumentOutOfRangeException();
}
public async Task<IEnumerable<MovieRequests>> GetNewRequests()
{
var allRequests = MovieRepository.GetWithUser();
return await allRequests.Where(x => !x.Approved && !x.Available).ToListAsync();
}
switch (vm.StatusFilter)
{
case FilterType.None:
break;
case FilterType.Approved:
requests = requests.Where(x => x.Approved);
break;
case FilterType.Processing:
requests = requests.Where(x => x.Approved && !x.Available);
break;
case FilterType.PendingApproval:
requests = requests.Where(x => !x.Approved && !x.Available && !(x.Denied ?? false));
break;
default:
throw new ArgumentOutOfRangeException();
}
public async Task<IEnumerable<MovieRequests>> GetAvailableRequests()
{
var allRequests = MovieRepository.GetWithUser();
return await allRequests.Where(x => !x.Approved && x.Available).ToListAsync();
return requests;
}
}
}

@ -0,0 +1,18 @@
namespace Ombi.Core.Models.Requests
{
public class FilterViewModel
{
public FilterType AvailabilityFilter { get; set; }
public FilterType StatusFilter { get; set; }
}
public enum FilterType
{
None = 0,
Available = 1,
NotAvailable = 2,
Approved = 3,
Processing = 4,
PendingApproval = 5
}
}

@ -12,8 +12,8 @@ namespace Ombi.Notifications
public void Setup(NotificationOptions opts, FullBaseRequest req, CustomizationSettings s)
{
ApplicationUrl = s.ApplicationUrl;
ApplicationName = string.IsNullOrEmpty(s.ApplicationName) ? "Ombi" : s.ApplicationName;
ApplicationUrl = s?.ApplicationUrl;
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
RequestedUser = string.IsNullOrEmpty(req.RequestedUser.Alias)
? req.RequestedUser.UserName
: req.RequestedUser.Alias;
@ -29,8 +29,8 @@ namespace Ombi.Notifications
public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s)
{
ApplicationUrl = s.ApplicationUrl;
ApplicationName = string.IsNullOrEmpty(s.ApplicationName) ? "Ombi" : s.ApplicationName;
ApplicationUrl = s?.ApplicationUrl;
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
RequestedUser = string.IsNullOrEmpty(req.RequestedUser.Alias)
? req.RequestedUser.UserName
: req.RequestedUser.Alias;
@ -47,8 +47,8 @@ namespace Ombi.Notifications
public void Setup(OmbiUser user, CustomizationSettings s)
{
ApplicationUrl = s.ApplicationUrl;
ApplicationName = string.IsNullOrEmpty(s.ApplicationName) ? "Ombi" : s.ApplicationName;
ApplicationUrl = s?.ApplicationUrl;
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
RequestedUser = user.UserName;
}

@ -115,3 +115,17 @@ export interface IEpisodesRequests {
export interface IMovieRequestModel {
theMovieDbId: number;
}
export interface IFilter {
availabilityFilter: FilterType;
statusFilter: FilterType;
}
export enum FilterType {
None = 0,
Available = 1,
NotAvailable = 2,
Approved = 3,
Processing = 4,
PendingApproval = 5,
}

@ -1,29 +1,34 @@
<div class="form-group">
<div>
<input type="text" id="search" class="form-control form-control-custom" placeholder="Search" (keyup)="search($event)">
</div>
<div class="input-group">
<input type="text" id="search" class="form-control form-control-custom searchwidth" placeholder="Search" (keyup)="search($event)">
<span class="input-group-btn"> <button class="btn btn-sm btn-info-outline" (click)="filterDisplay = true" >
<i class="fa fa-filter"></i> {{ 'Requests.Filter' | translate }}</button>
</span>
</div>
</div>
<br />
<div infinite-scroll
[infiniteScrollDistance]="1"
[infiniteScrollThrottle]="100"
(scrolled)="loadMore()">
<div infinite-scroll [infiniteScrollDistance]="1" [infiniteScrollThrottle]="100" (scrolled)="loadMore()">
<div *ngFor="let request of movieRequests">
<div class="row">
<div class="myBg backdrop" [style.background-image]="request.backgroundPath"></div>
<div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
<div class="myBg backdrop" [style.background-image]="request.backgroundPath"></div>
<div class="tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
<div class="col-sm-2 small-padding">
<img class="img-responsive poster" src="https://image.tmdb.org/t/p/w150/{{request.posterPath}}" alt="poster">
</div>
<div class="col-sm-5 small-padding">
<div class="col-sm-5 small-padding">
<div>
<a href="http://www.imdb.com/title/{{request.imdbId}}/" target="_blank">
<h4 class="request-title">{{request.title}} ({{request.releaseDate | date: 'yyyy'}})</h4>
@ -46,12 +51,16 @@
<span *ngIf="request.available" class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span>
<span *ngIf="request.approved && !request.available" id="processingRequestLabel" class="label label-info" [translate]="'Common.ProcessingRequest'"></span>
<span *ngIf="request.denied" class="label label-danger" id="requestDeclinedLabel" [translate]="'Common.RequestDenied'"></span>
<span *ngIf="request.deniedReason" title="{{request.deniedReason}}"><i class="fa fa-info-circle"></i></span>
<span *ngIf="!request.approved && !request.availble && !request.denied" id="pendingApprovalLabel" class="label label-warning" [translate]="'Common.PendingApproval'"></span>
<span *ngIf="request.deniedReason" title="{{request.deniedReason}}">
<i class="fa fa-info-circle"></i>
</span>
<span *ngIf="!request.approved && !request.availble && !request.denied" id="pendingApprovalLabel" class="label label-warning"
[translate]="'Common.PendingApproval'"></span>
</div>
<div *ngIf="request.denied" id="requestDenied">
{{ 'Requests.Denied' | translate }} <i style="color:red;" class="fa fa-check"></i>
{{ 'Requests.Denied' | translate }}
<i style="color:red;" class="fa fa-check"></i>
</div>
@ -60,8 +69,12 @@
<div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | date}}</div>
<br />
<div *ngIf="isAdmin">
<div *ngIf="request.qualityOverrideTitle">{{ 'Requests.QualityOverride' | translate }} <span>{{request.qualityOverrideTitle}} </span></div>
<div *ngIf="request.rootPathOverrideTitle">{{ 'Requests.RootFolderOverride' | translate }} <span>{{request.rootPathOverrideTitle}} </span></div>
<div *ngIf="request.qualityOverrideTitle">{{ 'Requests.QualityOverride' | translate }}
<span>{{request.qualityOverrideTitle}} </span>
</div>
<div *ngIf="request.rootPathOverrideTitle">{{ 'Requests.RootFolderOverride' | translate }}
<span>{{request.rootPathOverrideTitle}} </span>
</div>
</div>
</div>
@ -69,57 +82,71 @@
<div *ngIf="isAdmin">
<div *ngIf="!request.approved">
<form>
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}</button>
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit">
<i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}</button>
</form>
<!--Radarr Root Folder-->
<div *ngIf="radarrRootFolders" class="btn-group btn-split">
<button type="button" class="btn btn-sm btn-warning-outline"><i class="fa fa-plus"></i> {{ 'Requests.ChangeRootFolder' | translate }}</button>
<button type="button" class="btn btn-sm btn-warning-outline">
<i class="fa fa-plus"></i> {{ 'Requests.ChangeRootFolder' | translate }}</button>
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li *ngFor="let folder of radarrRootFolders"><a href="#" (click)="selectRootFolder(request, folder, $event)">{{folder.path}}</a></li>
<li *ngFor="let folder of radarrRootFolders">
<a href="#" (click)="selectRootFolder(request, folder, $event)">{{folder.path}}</a>
</li>
</ul>
</div>
<!--Radarr Quality Profiles -->
<div *ngIf="radarrProfiles" class="btn-group btn-split">
<button type="button" class="btn btn-sm btn-warning-outline"><i class="fa fa-plus"></i> {{ 'Requests.ChangeQualityProfile' | translate }}</button>
<button type="button" class="btn btn-sm btn-warning-outline">
<i class="fa fa-plus"></i> {{ 'Requests.ChangeQualityProfile' | translate }}</button>
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li *ngFor="let profile of radarrProfiles"><a href="#" (click)="selectQualityProfile(request, profile, $event)">{{profile.name}}</a></li>
<li *ngFor="let profile of radarrProfiles">
<a href="#" (click)="selectQualityProfile(request, profile, $event)">{{profile.name}}</a>
</li>
</ul>
</div>
<div *ngIf="!request.denied">
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> {{ 'Requests.Deny' | translate }}</button>
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny">
<i class="fa fa-times"></i> {{ 'Requests.Deny' | translate }}</button>
</div>
</div>
<form>
<button (click)="removeRequest(request)" style="text-align: right" class="btn btn-sm btn-danger-outline delete"><i class="fa fa-minus"></i> {{ 'Requests.Remove' | translate }}</button>
<button (click)="removeRequest(request)" style="text-align: right" class="btn btn-sm btn-danger-outline delete">
<i class="fa fa-minus"></i> {{ 'Requests.Remove' | translate }}</button>
</form>
<form>
<button *ngIf="request.available" (click)="changeAvailability(request, false)" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fa fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}</button>
<button *ngIf="!request.available" (click)="changeAvailability(request, true)" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change"><i class="fa fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}</button>
<button *ngIf="request.available" (click)="changeAvailability(request, false)" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change">
<i class="fa fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}</button>
<button *ngIf="!request.available" (click)="changeAvailability(request, true)" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change">
<i class="fa fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}</button>
</form>
</div>
<div class="dropdown" *ngIf="issueCategories && issuesEnabled">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> Report Issue
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true">
<i class="fa fa-plus"></i> {{ 'Requests.ReportIssue' | translate }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li *ngFor="let cat of issueCategories"><a [routerLink]="" (click)="reportIssue(cat, request)">{{cat.value}}</a></li>
<li *ngFor="let cat of issueCategories">
<a [routerLink]="" (click)="reportIssue(cat, request)">{{cat.value}}</a>
</li>
</ul>
</div>
@ -136,4 +163,47 @@
<issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequest?.title"
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]=""></issue-report>
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]=""></issue-report>
<p-sidebar [(visible)]="filterDisplay" styleClass="ui-sidebar-md side-back side-small">
<h3>{{ 'Filter.Filter' | translate }}</h3>
<hr>
<h4>{{ 'Filter.FilterHeaderAvailability' | translate }}</h4>
<div class="form-group">
<div class="radio">
<input type="radio" id="Available" name="Availability" (click)="filterAvailability(filterType.Available)">
<label for="Available">{{ 'Common.Available' | translate }}</label>
</div>
</div>
<div class="form-group">
<div class="radio">
<input type="radio" id="notAvailable" name="Availability" (click)="filterAvailability(filterType.NotAvailable)">
<label for="notAvailable">{{ 'Common.NotAvailable' | translate }}</label>
</div>
</div>
<h4>{{ 'Filter.FilterHeaderRequestStatus' | translate }}</h4>
<div class="form-group">
<div class="radio">
<input type="radio" id="approved" name="Status" (click)="filterStatus(filterType.Approved)">
<label for="approved">{{ 'Filter.Approved' | translate }}</label>
</div>
</div>
<div class="form-group">
<div class="radio">
<input type="radio" id="notApproved" name="Status" (click)="filterStatus(filterType.NotApproved)">
<label for="notApproved">{{ 'Filter.NotApproved' | translate }}</label>
</div>
</div>
<div class="form-group">
<div class="radio">
<input type="radio" id="Processing" name="Status" (click)="filterStatus(filterType.Processing)">
<label for="Processing">{{ 'Common.ProcessingRequest' | translate }}</label>
</div>
</div>
<button class="btn btn-sm btn-primary-outline" (click)="clearFilter()">
<i class="fa fa-filter"></i> {{ 'Requests.ClearFilter' | translate }}</button>
</p-sidebar>

@ -8,7 +8,7 @@ import { Subject } from "rxjs/Subject";
import { AuthService } from "../auth/auth.service";
import { NotificationService, RadarrService, RequestService } from "../services";
import { IIssueCategory, IMovieRequests, IRadarrProfile, IRadarrRootFolder } from "../interfaces";
import { FilterType, IFilter, IIssueCategory, IMovieRequests, IRadarrProfile, IRadarrRootFolder } from "../interfaces";
@Component({
selector: "movie-requests",
@ -32,6 +32,10 @@ export class MovieRequestsComponent implements OnInit {
public issueProviderId: string;
public issueCategorySelected: IIssueCategory;
public filterDisplay: boolean;
public filter: IFilter;
public filterType = FilterType;
private currentlyLoaded: number;
private amountToLoad: number;
@ -62,6 +66,9 @@ export class MovieRequestsComponent implements OnInit {
this.currentlyLoaded = 100;
this.loadInit();
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
this.filter = {
availabilityFilter: FilterType.None,
statusFilter: FilterType.None};
}
public loadMore() {
@ -139,6 +146,32 @@ export class MovieRequestsComponent implements OnInit {
event.preventDefault();
}
public clearFilter() {
this.filterDisplay = false;
this.filter.availabilityFilter = FilterType.None;
this.filter.statusFilter = FilterType.None;
this.resetSearch();
}
public filterAvailability(filter: FilterType) {
this.filter.availabilityFilter = filter;
this.requestService.filterMovies(this.filter)
.subscribe(x => {
this.setOverrides(x);
this.movieRequests = x;
});
}
public filterStatus(filter: FilterType) {
this.filter.statusFilter = filter;
this.requestService.filterMovies(this.filter)
.subscribe(x => {
this.setOverrides(x);
this.movieRequests = x;
});
}
private loadRequests(amountToLoad: number, currentlyLoaded: number) {
this.requestService.getMovieRequests(amountToLoad, currentlyLoaded + 1)
.subscribe(x => {

@ -6,7 +6,7 @@ import { Observable } from "rxjs/Rx";
import { TreeNode } from "primeng/primeng";
import { IRequestEngineResult } from "../interfaces";
import { IChildRequests, IMovieRequestModel, IMovieRequests, IMovieUpdateModel, ITvRequests, ITvUpdateModel } from "../interfaces";
import { IChildRequests, IFilter, IMovieRequestModel, IMovieRequests, IMovieUpdateModel, ITvRequests, ITvUpdateModel } from "../interfaces";
import { ISearchTvResult } from "../interfaces";
import { ServiceHelpers } from "./service.helpers";
@ -106,4 +106,7 @@ export class RequestService extends ServiceHelpers {
public deleteChild(child: IChildRequests): Observable<boolean> {
return this.http.delete<boolean>(`${this.url}tv/child/${child.id}`, {headers: this.headers});
}
}
public filterMovies(filter: IFilter): Observable<IMovieRequests[]> {
return this.http.post<IMovieRequests[]>(`${this.url}movie/filter`, JSON.stringify(filter), {headers: this.headers});
}
}

@ -1,6 +1,6 @@
<settings-menu></settings-menu>
<wiki [url]="'https://github.com/tidusjar/Ombi/wiki/User-Management-Settings'"></wiki>
<wiki [url]="'https://github.com/tidusjar/Ombi/wiki/User-Importer-Settings'"></wiki>
<fieldset *ngIf="settings">
<legend>User Importer Settings</legend>

@ -1,6 +1,7 @@
<p-sidebar [(visible)]="visible" position="right" styleClass="ui-sidebar-md side-back" (onHide)="hide()">
<div *ngIf="title">
<h3>Reporting an Issue for "{{title}}"</h3>
<h4 *ngIf="issueCategory">Issue type: {{issueCategory.value}}</h4>

@ -332,6 +332,10 @@ button.list-group-item:focus {
box-shadow: 0px 0px 3.5em #000000;
}
.side-small {
width:20em $i;
}
.ui-widget-overlay .ui-sidebar-mask {
background: black;
}

@ -421,6 +421,43 @@ $border-radius: 10px;
margin-bottom: 10px;
}
.radio label {
display: inline-block;
cursor: pointer;
position: relative;
padding-left: 25px;
margin-right: 15px;
font-size: 13px;
margin-bottom: 10px;
}
.radio label:before {
content: "";
display: inline-block;
width: 18px;
height: 18px;
margin-right: 10px;
position: absolute;
left: 0;
bottom: 1px;
border: 2px solid #eee;
border-radius: 3px;
}
.radio input[type=radio] {
display: none;
}
.radio input[type=radio]:checked + label:before {
content: "\2713";
font-size: 13px;
color: #fafafa;
text-align: center;
line-height: 13px;
}
.small-checkbox label:before {
content: "";
display: inline-block;
@ -901,4 +938,8 @@ a > h4:hover {
content: attr(data-label)
}
}
}
.searchWidth {
width: 94%;
}

@ -328,40 +328,15 @@ namespace Ombi.Controllers
return movies || tv;
}
///// <summary>
///// Gets the specific grid model for the requests (for modelling the UI).
///// </summary>
///// <returns></returns>
//[HttpGet("tv/grid")]
//[ApiExplorerSettings(IgnoreApi = true)]
//public async Task<RequestGridModel<TvRequests>> GetTvRequestsGrid()
//{
// return await GetGrid(TvRequestEngine);
//}
///// <summary>
///// Gets the specific grid model for the requests (for modelling the UI).
///// </summary>
///// <returns></returns>
//[HttpGet("movie/grid")]
//[ApiExplorerSettings(IgnoreApi = true)]
//public async Task<RequestGridModel<MovieRequests>> GetMovieRequestsGrid()
//{
// return await GetGrid(MovieRequestEngine);
//}
//private async Task<RequestGridModel<T>> GetGrid<T>(IRequestEngine<T> engine) where T : BaseRequestModel
//{
// var allRequests = await engine.GetRequests();
// var r = allRequests.ToList();
// var model = new RequestGridModel<T>
// {
// Available = r.Where(x => x.Available && !x.Approved),
// Approved = r.Where(x => x.Approved && !x.Available),
// New = r.Where(x => !x.Available && !x.Approved)
// };
// return model;
//}
/// <summary>
/// Returns a filtered list
/// </summary>
/// <param name="vm"></param>
/// <returns></returns>
[HttpPost("movie/filter")]
public IEnumerable<MovieRequests> Filter([FromBody] FilterViewModel vm)
{
return MovieRequestEngine.Filter(vm);
}
}
}

@ -12,6 +12,7 @@
"Common": {
"ContinueButton": "Continue",
"Available": "Available",
"NotAvailable": "Not Available",
"ProcessingRequest": "Processing Request",
"PendingApproval": "Pending Approval",
"RequestDenied":"Request Denied",
@ -110,7 +111,9 @@
"Season":"Season:",
"GridTitle":"Title",
"AirDate":"AirDate",
"GridStatus":"Status"
"GridStatus":"Status",
"ReportIssue":"Report Issue",
"Filter":"Filter"
},
"Issues":{
"Title":"Issues",
@ -130,5 +133,11 @@
"Comments":"Comments",
"WriteMessagePlaceholder":"Write your message here...",
"ReportedBy":"Reported By"
},
"Filter":{
"ClearFilter":"Clear Filter",
"FilterHeaderAvailability":"Availability",
"FilterHeaderRequestStatus":"Status",
"Approved":"Approved"
}
}

Loading…
Cancel
Save