It features the ability to sticky notes to the top, and invalidate the notes if the user is an admin (but other groups can be added in the config, which we'll see later.)
Lastly before we get started please note that I am not a professional developer, this is my first time ever contributing to an open source project and this code should be heavily reviewed before being used in a production environment. You'll also see mentions of SBI which is the company I work, feel free to disregard.
First we need to create a table in our database like so:
Code: Select all
# Name Type Collation Attributes Null Default Extra
1 id bigint(20) No None AUTO_INCREMENT
2 company_id varchar(150) utf8_general_ci No None
3 subject text utf8_general_ci Yes NULL
4 body mediumtext utf8_general_ci Yes NULL
5 content_path varchar(250) utf8_general_ci Yes NULL
6 content_type varchar(250) utf8_general_ci Yes NULL
7 valid_id smallint(6) No None
8 create_time datetime No None
9 create_by int(11) No None
10 change_time datetime No None
11 change_by int(11) No None
12 sticky tinyint(1) No None
Next is the config file, otrs/Kernel/Config/Files/SBICustom.xml
Code: Select all
<?xml version="1.0" encoding="UTF-8" ?>
<otrs_config version="1.0" init="Application">
<ConfigItem Name="AgentCustomerInformationCenter::Backend###CompanyNotes" Required="1" Valid="1">
<Description Lang="en">Registration for custom SBI Module to create and display notes for CustomerCompany's, referred to as Site Codes within SBI.</Description>
<Group>SBICustom</Group>
<SubGroup>Frontend::Agent::Dashboard</SubGroup>
<Setting>
<Hash>
<Item Key="Module">Kernel::Output::HTML::DashboardCompanyNotes</Item>
<Item Key="Title" Translatable="1">Customer Company Notes</Item>
<Item Key="Description" Translatable="1">Customer Company Notes</Item>
<Item Key="Attributes"></Item>
<Item Key="Block">ContentSmall</Item>
<Item Key="Group"></Item>
<Item Key="Default">1</Item>
</Hash>
</Setting>
</ConfigItem>
<ConfigItem Name="Kernel::Output::HTML::DashboardCompanyNotes::PermissionGroup" Required="1" Valid="1">
<Description Translatable="1">Specifies the group that can delete and sticky notes in CompanyNotes module.</Description>
<Group>SBICustom</Group>
<SubGroup>Frontend::Agent::Dashboard</SubGroup>
<Setting>
<String Regex="">admin</String>
</Setting>
</ConfigItem>
</otrs_config>
This needs to be added at the top, right after "use warnings;" will be fine. I've placed comments around my modifications.
Code: Select all
package Kernel::System::CustomerCompany;
use strict;
use warnings;
##SBI MOD - Include custom CompanyNotes module##
use Kernel::System::CustomerCompany::CompanyNotes;
##SBI MOD##
use Kernel::System::Valid;
use Kernel::System::Cache;
Code: Select all
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
##SBI MOD - Include custom CompanyNotes module##
@ISA = ('Kernel::System::CustomerCompany::CompanyNotes');
##SBI MOD##
# check needed objects
for (qw(DBObject ConfigObject LogObject MainObject EncodeObject)) {
$Self->{$_} = $Param{$_} || die "Got no $_!";
}
Code: Select all
#SBI MOD -
#Kernel/System/CustomerCompany/CompanyNotes.pm - Module to load CompanyNotes for CustomerCompany object.
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (AGPL). If you
# did not receive this file, see http://www.gnu.org/licenses/agpl.txt.
# --
package Kernel::System::CustomerCompany::CompanyNotes;
use strict;
use warnings;
use Kernel::System::User;
use Kernel::System::Group;
use Kernel::System::Cache;
use Kernel::System::Web::UploadCache;
use Kernel::System::HTMLUtils;
use vars qw($VERSION);
$VERSION = qw($Revision: 1.328 $) [1];
sub CompanyNoteGet {
my ( $Self, %Param ) = @_;
if ( !$Param{CompanyArticleID}) {
$Self->{LogObject}->Log( Priority => 'error', Message => 'Need CompanyArticleID!' );
return;
}
# sql query
my @Content;
my @Bind;
my $SQL = '
SELECT id, company_id, subject, body, valid_id, create_time, create_by, change_time, change_by, sticky FROM company_article WHERE id = ?';
push @Bind, \$Param{CompanyArticleID};
return if !$Self->{DBObject}->Prepare( SQL => $SQL, Bind => \@Bind,);
if (my @Row = $Self->{DBObject}->FetchrowArray()) {
my %Data = (
NoteID => $Row[0],
CustomerID => $Row[1],
Subject => $Row[2],
Body => $Row[3],
Valid => $Row[4],
CreateTime => $Row[5],
CreateBy => $Row[6],
ChangeTime => $Row[7],
ChangeBy => $Row[8],
Sticky => $Row[9],
);
return %Data;
# return if content is empty
} else {
# Log an error only if a specific article was requested and there is no filter active.
if ( $Param{CompanyArticleID}) {
$Self->{LogObject}->Log(
Priority => 'error',
Message => "No such article for CompanyArticleID ($Param{CompanyArticleID})!",
);
}
return;
}
} ##End CompanyNoteGet function definition
sub CompanyNoteIndex {
my ( $Self, %Param ) = @_;
my @Index;
my @Bind;
my $Limit;
if ( !$Param{CustomerID}) {
$Self->{LogObject}->Log( Priority => 'error', Message => 'CompanyNoteIndex: Need CustomerID!' );
return;
}
if ($Param{Limit}){ $Limit = $Param{Limit}; }
else { $Limit = 1000;}
$Bind[0]= \$Param{CustomerID};
#We need to do two searches, once where we search for sticky notes (Sticky = 1)
#and a second time for regular notes (Sticky = 0), this way the sticky notes
#will always be at the top of our results. We'll do this with the following
#foreach loop.
foreach my $i (1, 0){
##Don't do another search if we already hit our limit.
next if $Limit <= 0;
##For our Bind variable, our site code should already be in position 0,
##here we're putting our bind value for weather or not we want sticky notes.
##The foreach loop will search 1 first for sticky notes, the 0 for other notes.
$Bind[1] = \$i;
#SQL Query
my $SQL ='
SELECT id FROM company_article WHERE company_id = ? AND sticky = ?';
if (!$Param{ShowInvalid}) {
$SQL .= ' AND valid_id = 1';
}
$SQL .= ' ORDER BY create_time DESC';
return if !$Self->{DBObject}->Prepare( SQL => $SQL, Bind => \@Bind, Limit => $Limit);
while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { push @Index, $Row[0];
$Limit--;}
}
return @Index;
} ##End CompanyNoteIndex function definition
sub CompanyNoteContentIndex {
my ( $Self, %Param ) = @_;
my @NoteContent;
my @NoteIndex;
if (!$Param{CustomerID} && !$Param{NoteIndex} && ref($Param{NoteIndex}) ne "ARRAY") {
$Self->{LogObject}->Log( Priority => 'error', Message => 'CustomerNotes.pm: Need CustomerID or ContentIndex!' );
return;
}
##If we weren't provided a list of tickets, or what we were provided is not an array
if (!$Param{NoteIndex} || ref($Param{NoteIndex}) ne "ARRAY"){
@NoteIndex = $Self->CompanyNoteIndex( CustomerID => $Param{CustomerID},)
} else {
@NoteIndex = @{$Param{NoteIndex}}
}
##Once we have our ticket list, run a search on the tickts.
if(@NoteIndex) {
my $Count = 0;
foreach my $NoteID (@NoteIndex){
my %Content = $Self->CompanyNoteGet( CompanyArticleID => $NoteID, );
push(@NoteContent, {%Content});
}
return @NoteContent; } else { return; }
} ##End CompanyNoteConIndex function definition
sub CompanyNoteCreate {
my ( $Self, %Param ) = @_;
for (qw(CustomerID UserID NoteSubject NoteBody)) {
my $key = $_;
if ( !$Param{$key}) {
$Self->{LogObject}->Log( Priority => 'error', Message => "CreateCompanyNote: Need $key !");
return;
} }
return if !$Self->{DBObject}->Do(
SQL => 'INSERT INTO company_article '
. '(company_id, subject, body, valid_id, create_time, create_by, change_time, change_by, '
. 'sticky)'
. ' VALUES (?, ?, ?, 1, current_timestamp, ?, current_timestamp, ?, 0)' ,
Bind => [
\$Param{CustomerID}, \$Param{NoteSubject}, \$Param{NoteBody},
\$Param{UserID}, \$Param{UserID},
],
);
return 1;
} ##End CompanyNoteCreate definition.
sub CompanyNoteInvalidate {
my ( $Self, %Param ) = @_;
##Check to make sure we have all needed items.
for (qw(CompanyArticleID CustomerID UserID)) {
my $key = $_;
if ( !$Param{$key}) {
$Self->{LogObject}->Log( Priority => 'error', Message => "CompanyNoteInvalidate: Need $key !");
return 0;
} }
##Get the note's content so we can check it and log it.
my %Note =$Self->CompanyNoteGet(
CompanyArticleID => $Param{CompanyArticleID}
);
##Check to make sure that the note exists, that it is still valid and that the CustomerID
##provided matches the one on the note.
if(!%Note || !$Note{Valid} || $Note{CustomerID} ne $Param{CustomerID}){ return; }
##Do the SQL query
return if !$Self->{DBObject}->Do(
SQL => 'UPDATE company_article SET valid_id = 0, change_time = current_timestamp, change_by = ? WHERE id = ? AND company_id = ?',
Bind =>[
\$Param{UserID}, \$Param{CompanyArticleID}, \$Param{CustomerID},
],
);
##Log the note being invalidated, mostly for auditing purposes.
$Self->{LogObject}->Log(
Priority => 'notice',
Message => "Note was invalidated by UserID: $Param{UserID} CustomerID: $Param{CustomerID} NoteSubject: $Note{Subject} NoteBody: $Note{Body}",
);
return 1;
} ##End CompanyNoteInvalidate definition.
sub CompanyNoteSticky {
my ( $Self, %Param ) = @_;
my $Sticky = 0;
##Check to make sure we have all needed items.
for (qw(CustomerID CompanyArticleID UserID)) {
my $key = $_;
if ( !$Param{$key}) {
$Self->{LogObject}->Log( Priority => 'error', Message => "CompanyNoteSticky: Need $key !");
return;
} }
my %Note =$Self->CompanyNoteGet(
CompanyArticleID => $Param{CompanyArticleID}
);
##Check to make sure that the note exists and that the CustomerID
##provided matches the one on the note.
if(!%Note || $Note{CustomerID} ne $Param{CustomerID}){ return; }
##Check to see if the note is stickied or not already.
if(!$Note{Sticky}){ $Sticky = 1;}
return if !$Self->{DBObject}->Do(
SQL => 'UPDATE company_article SET sticky = ?, change_time = current_timestamp, change_by = ? WHERE id = ? AND company_id = ?',
Bind =>[
\$Sticky, \$Param{UserID}, \$Param{CompanyArticleID}, \$Param{CustomerID},
],
);
return 1;
}
1;
Code: Select all
##SBI MOD - Custom module to allow creation and retrieval of CustomerCompanyNotes on the CustomerCompany Information Center.
##This is the Controller, it is loaded by the sysonfig backend.
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (AGPL). If you
# did not receive this file, see http://www.gnu.org/licenses/agpl.txt.
# --
package Kernel::Output::HTML::DashboardCompanyNotes;
use Kernel::System::Web::UploadCache;
use strict;
use warnings;
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {%Param};
bless( $Self, $Type );
#
for my $Needed (
qw(ParamObject DBObject CustomerCompanyObject LayoutObject LogObject QueueObject UserObject ConfigObject GroupObject)
)
{
if ( !$Self->{$Needed} ) {
$Self->{LayoutObject}->FatalError( Message => "Got no $Needed!" );
}
}
$Self->{PrefKey} = 'UserDashboardPref' . $Self->{Name} . '-Shown';
$Self->{PageShown} = $Self->{LayoutObject}->{ $Self->{PrefKey} } || $Self->{Config}->{Limit};
$Self->{StartHit} = int( $Self->{ParamObject}->GetParam( Param => 'StartHit' ) || 1 );
$Self->{NoteAction} = $Self->{ParamObject}->GetParam( Param => 'NoteAction' );
return $Self;
}
sub Preferences {
my ( $Self, %Param ) = @_;
my @Params = (
{
Desc => 'Shown Tickets',
Name => $Self->{PrefKey},
Block => 'Option',
# Block => 'Input',
Data => {
5 => ' 5',
10 => '10',
},
SelectedID => $Self->{PageShown},
Translation => 0,
},
);
return @Params;
}
sub Config {
my ( $Self, %Param ) = @_;
return (
%{ $Self->{Config} },
# caching not needed
CacheKey => undef,
CacheTTL => undef,
);
}
sub Run {
my ( $Self, %Param ) = @_;
return if !$Param{CustomerID};
##Check the config for group permissions to edit, invalidate and sticky notes. This is by default, the 'admin' group. This setting can be accessed
##in the SBICustom sysconfig category.
my $NotesAdminGroupID = $Self->{GroupObject}->GroupLookup(
Group => $Self->{ConfigObject}->Get('Kernel::Output::HTML::DashboardCompanyNotes::PermissionGroup'),
);
# get user groups, where the user has the rw privilege
my %Groups = $Self->{GroupObject}->GroupMemberList(
UserID => $Self->{UserID},
Type => 'rw',
Result => 'HASH',
);
# if the user is a member in this group they can access the feature
if ( $Groups{$NotesAdminGroupID} ) {
$Self->{NotesAdmin} = 1;
}
##Note creation
if ($Self->{NoteAction} && $Self->{NoteAction} eq 'CompanyNoteCreate'){
my %GetParam;
for my $Key (qw(NoteSubject NoteBody))
{
$GetParam{$Key} = $Self->{ParamObject}->GetParam( Param => $Key );
}
##Need to add error checking. CompanyNoteCreate should return 1 or 0 based on success.
my $AddNote = $Self->{CustomerCompanyObject}->CompanyNoteCreate (
CustomerID => $Param{CustomerID},
UserID => $Self->{UserID},
NoteSubject => $GetParam{NoteSubject},
NoteBody => $GetParam{NoteBody},
);
} ## End CompanyNoteCreate if branch.
##Note invalidation
if ($Self->{NoteAction} && $Self->{NoteAction} eq 'CompanyNoteInvalidate') {
##Check permissions..
if($Self->{NotesAdmin}){
my %GetParam;
for my $Key (qw(CompanyArticleID CustomerID)) {
$GetParam{$Key} = $Self->{ParamObject}->GetParam( Param => $Key );
}
##Try to invalidate the note...
my $InvalidateNote = $Self->{CustomerCompanyObject}->CompanyNoteInvalidate(
CompanyArticleID => $GetParam{CompanyArticleID},
CustomerID => $GetParam{CustomerID},
UserID => $Self->{UserID},
);
##If note invalidate was successfull...
if($InvalidateNote){
$Self->{LayoutObject}->Block(
Name => 'Notification',
Data => {Message => 'Note successfully invalidated!'},
);
##Otherwise..
} else {
$Self->{LayoutObject}->Block(
Name => 'Notification',
Data => {Error => 'There was a problem invalidating the note! (It is probably already invalidated, doesn\'t exist or you don\'t have permission.)'},
);
}
##If we don't have permission..
} else {
$Self->{LayoutObject}->Block(
Name => 'Notification',
Data => {Error => 'You don\'t have permission to do that!'},
);
}} ## End CompanyNoteInvalidate if branch.
##Sticky a note
if ($Self->{NoteAction} && $Self->{NoteAction} eq 'CompanyNoteSticky'){
##Check permissions..
if($Self->{NotesAdmin}){
my %GetParam;
for my $Key (qw(CompanyArticleID CustomerID)) {
$GetParam{$Key} = $Self->{ParamObject}->GetParam( Param => $Key );
}
##Try to stick the note...
my $StickyNote = $Self->{CustomerCompanyObject}->CompanyNoteSticky(
CustomerID => $GetParam{CustomerID},
CompanyArticleID => $GetParam{CompanyArticleID},
UserID=> $Self->{UserID},
);
##If note sticky was successfull...
if($StickyNote){
$Self->{LayoutObject}->Block(
Name => 'Notification',
Data => {Message => 'Note successfully stickied!'},
);
##Otherwise...
} else {
$Self->{LayoutObject}->Block(
Name => 'Notification',
Data => {Error => 'There was a problem stickying the note! ',});
}
##If we don't have permission..
} else {
$Self->{LayoutObject}->Block(
Name => 'Notification',
Data => {Error => 'You don\'t have permission to do that!'},
);
}} ## End CompanyNoteSticky if branch.
##Get our index of notes...
my @NoteIndex = $Self->{CustomerCompanyObject}->CompanyNoteIndex(
CustomerID => $Param{CustomerID}
);
##If we have notes, start working through them below.
if(@NoteIndex){
##Get a count of the number of notes we got back.
my $Total = scalar @NoteIndex;
##Help build the URL for our AJAX request
my $LinkPage
= 'Subaction=Element;Name='
. $Self->{Name} . ';'
. 'CustomerID='
. $Self->{LayoutObject}->LinkEncode( $Param{CustomerID} ) . ';';
##List Layout Object method creates the pagination, I'll be damned if I know it works
my %PageNav = $Self->{LayoutObject}->PageNavBar(
StartHit => $Self->{StartHit}, # start to show items
PageShown => $Self->{PageShown}, # number of shown items a page
AllHits => $Total || 1, # number of total hits
Action => 'Action=' . $Self->{LayoutObject}->{Action}, # e. g. 'Action=' . $Self->{LayoutObject}->{Action}
Link => $LinkPage, # e. g. 'Subaction=View;'
AJAXReplace => 'Dashboard'.$Self->{Name}, # IDElement which should be replaced
IDPrefix => 'Dashboard'.$Self->{Name}, # Prefix for the id parameter
KeepScriptTags => $Param{AJAX},
);
$Self->{LayoutObject}->Block(
Name => 'CompanyNotesNavBar',
Data => {
%{ $Self->{Config} },
Name => $Self->{Name},
%PageNav,
},
);
##Splice through our NoteIndex array and grab only the notes for the page we're loading.
@NoteIndex = splice @NoteIndex, $Self->{StartHit} -1, $Self->{PageShown};
$Self->{LayoutObject}->Block(
Name=>"DashboardCompanyNotesBox"
);
##Start working through the notes
for my $Note (@NoteIndex){
##Use CompanyNoteGet to retrieve the contents of each note
my %NoteContent = $Self->{CustomerCompanyObject}->CompanyNoteGet(
CompanyArticleID => $Note,
);
##Get the user's real name in a human readable format, if possible.
my $UserName;
if ( my %User = $Self->{UserObject}->GetUserData( UserID => $NoteContent{CreateBy},) ){
$UserName = $User{UserFirstname}." ".$User{UserLastname};
} else {
$UserName = $NoteContent{ChangeBy};
}
##Send Note content to output
$Self->{LayoutObject}->Block(
Name => 'DashboardCompanyNotesBoxRow',
Data => {
Subject => $NoteContent{Subject},
Body => $NoteContent{Body},
User => $UserName,
Date => $NoteContent{CreateTime},
},);
##If the note is a sticky, set it's CSS class.
if($NoteContent{Sticky}){
$Self->{LayoutObject}->Block(
Name => 'DashboardCompanyNotesBoxRowSticky',
Data =>{
StickyClass => "StickyNote",
},
);
}
##If user has admin rights, send admin options.
if($Self->{NotesAdmin}){
$Self->{LayoutObject}->Block(
Name => 'DashboardCompanyNotesBoxRowAdmin',
Data => {NoteID => $NoteContent{NoteID},
CustomerID => $Param{CustomerID}
}
);
}
}
##If we don't have any notes, display a default message.
} else {
$Self->{LayoutObject}->Block(
Name => 'Notification',
Data => { Message => "No notes yet for this office! Please add one below:",},
);}
##This bit feeds our CustomerID to our HTML form, that is all.
$Self->{LayoutObject}->Block(
Name => 'DashboardCompanyNotesForm',
Data => {CustomerID => $Param{CustomerID},},
);
##Flip the content onto the screen.
my $Content = $Self->{LayoutObject}->Output(
TemplateFile => 'CompanyNotes',
Data => {
%{ $Self->{Config} },
Name => $Self->{Name},
},
KeepScriptTags => $Param{AJAX},
);
return $Content;
}
sub _AJAXUpdate {
}
1;
Code: Select all
<script type="text/javascript">
function confirmDel()
{
return confirm('Are you sure you want to invalidate(hide) this note?');
}
function showAdd()
{
$('#AddNote').toggle();
$('#AddButton').toggle();
}
</script>
<style type="text/css">
.StickyNote.CompanyNotesTR {
Background: #FCF0AD;
}
.CompanyNotesTR {
Background: #FFFFFF;
border-bottom: 1px solid black;
}
.StickyNote.Even.CompanyNotesTR{
Background: #FAFAD2;
}
.Even.CompanyNotesTR {
Background: #EDEDED;
}
.CompanyNotesTD {
padding: 10px 4px 10px 4px;
}
.CompanyNotesTR.Last {
border: 0;
}
.CompanyNotesFoot {
font-size: 10px;
}
.CompanyNotesSubject {
padding-bottom: 4px;
border-bottom: 1px dashed black;
font-weight: bold;
}
</style>
<div class="Clear"></div>
<!-- dtl:block:Notification -->
<span class="CompanyNotesError">$QData{"Error"}</span>
<span class="CompanyNoteNotice">$QData{"Message"}</span>
<!-- dtl:block:Notification -->
<div class="Clear"></div>
<!-- dtl:block:CompanyNotesNavBar -->
<span class="Pagination">
$Data{"SiteNavBar"}
</span>
<!-- dtl:block:CompanyNotesNavBar -->
<div class="Clear"></div>
<!-- dtl:block:DashboardCompanyNotesBox -->
<table id="CompanyNotesTable">
<tbody>
<!-- dtl:block:DashboardCompanyNotesBoxRow -->
<tr class="CompanyNotesTR
<!-- dtl:block:DashboardCompanyNotesBoxRowSticky -->
StickyNote
<!-- dtl:block:DashboardCompanyNotesBoxRowSticky -->
"><td class="CompanyNotesTD">
<H2 Class = "CompanyNotesSubject">$QData{"Subject"}</H2>
<P Class = "CompanyNotesBody">$QData{"Body"}</P><BR>
<hr>
<SPAN Class = "CompanyNotesFoot">Note added: $TimeShort{"$Data{"Date"}"} by $QData{"User"}
<!-- dtl:block:DashboardCompanyNotesBoxRowAdmin -->
| <a id="InvalidateNote$Data{"NoteID"}" href="#">Invalidate Note</a> -
<!-- dtl:js_on_document_complete -->
<script type="text/javascript">//<![CDATA[
$('#InvalidateNote$Data{"NoteID"}').unbind('click').bind('click', function(){
if (confirmDel()){
var $Container = $(this).parents('.WidgetSimple');
$Container.addClass('Loading');
Core.AJAX.ContentUpdate($('#DashboardCompanyNotes'), '/otrs/index.pl?Action=AgentCustomerInformationCenter;Subaction=Element;Name=CompanyNotes;NoteAction=CompanyNoteInvalidate;CompanyArticleID=$Data{"NoteID"};CustomerID=$Data{"CustomerID"};', function () {
Core.UI.Table.InitCSSPseudoClasses($('#DashboardCompanyNotes'));
$Container.removeClass('Loading');
});
}
return false;
});
//]]></script>
<!-- dtl:js_on_document_complete -->
<a id="StickyNote$Data{"NoteID"}" href="#">Sticky Note</a>
<!-- dtl:js_on_document_complete -->
<script type="text/javascript">//<![CDATA[
$('#StickyNote$Data{"NoteID"}').unbind('click').bind('click', function(){
var $Container = $(this).parents('.WidgetSimple');
$Container.addClass('Loading');
Core.AJAX.ContentUpdate($('#DashboardCompanyNotes'), '/otrs/index.pl?Action=AgentCustomerInformationCenter;Subaction=Element;Name=CompanyNotes;NoteAction=CompanyNoteSticky;CompanyArticleID=$Data{"NoteID"};CustomerID=$Data{"CustomerID"};', function () {
Core.UI.Table.InitCSSPseudoClasses($('#DashboardCompanyNotes'));
$Container.removeClass('Loading');
});
return false;
});
//]]></script>
<!-- dtl:js_on_document_complete -->
<!-- dtl:block:DashboardCompanyNotesBoxRowAdmin -->
</SPAN></td></tr>
<!-- dtl:block:DashboardCompanyNotesBoxRow -->
</tbody>
</table><BR/>
<!-- dtl:block:DashboardCompanyNotesBox -->
<BR>
<button onclick="showAdd()" id="AddButton">Add Note</button>
<BR>
<!-- dtl:block:DashboardCompanyNotesForm -->
<div id="AddNote" style="display:none;">
<form action="$Env{"CGIHandle"}?Action=$Env{"Action"};CustomerID=$Data{"CustomerID"}" method="post" enctype="multipart/form-data" name="compose" id="NewCompanyNotes" class="Validate PreventMultipleSubmits">
<input type="hidden" name="Action" id="Action" value="$Env{"Action"}"/>
<input type="hidden" name="CustomerID" id="CustomerID" value="$Data{"CustomerID"}" />
<input type="hidden" name="NoteAction" id="NoteAction" value="CompanyNoteCreate" />
<input type="hidden" name="FormID" id="FormID" value="$QData{"FormID"}"/>
<input type="hidden" name="UserID" id="UserID" value='$QData{"UserID"}' />
<label for="NoteSubject">Note Subject: </label><BR>
<input type="text" name="NoteSubject" id="NoteSubject" class="Validate_Required"></input><BR>
<label for="NoteBody">Note Body: </label><BR>
<textarea name="NoteBody" id="NoteBody" value="Body" class="Validate_Required"></textarea><BR>
<input id ="CompanyNotesSave" type="submit" Value="Save Note" ></input> <button onclick="showAdd()">Cancel</button>
</form>
</div>
<!-- dtl:block:DashboardCompanyNotesForm -->